Last updated Apr 2, 2024

Writing a Space Admin screen

Applicable:

This tutorial applies to Confluence 7.5 and later

Level of experience:

This is an intermediate tutorial. You should have completed at least one beginner tutorial before working through this tutorial. See the list of developer tutorials.

This tutorial will guide you through the process of adding a new screen in Space Tools. This is useful if you need to provide the ability for your app to be configured on a space-by-space basis.

There are two ways you can do this:

  • using a Struts action
  • using a servlet

We'll describe both methods below.

You can use any renderer (Velocity, Soy, etc) as long as you provide the correct decorator name in the HTML output.

Before you begin

To complete this tutorial, you should:

Plugin source

If you want to skip ahead or check your work when you finish, you can find the plugin source code on Atlassian Bitbucket. Alternatively, you can download the source as a ZIP archive.  To clone the repository, issue the following command:

1
2
git clone https://bitbucket.org/atlassian_tutorial/space-plugin-example.git

Create a Space Tools screen using a Struts action

1. Create a web-item

First we need to add a web-item for Space Tools in atlassian-plugin.xml. If the screen should only be visible to space admins, you will need to use a Condition to check this.

atlassian-plugin.xml

1
2
   <!-- Item in Space Tools -->
 <web-item key="space-admin-quick-link-manager" name="Quick Link Manager in Space Admin" section="system.space.tools/addons" weight="100">
     <label key="space.admin.quick.link.manager" />
     <link linkId="space-admin-quick-link-manager-id">/plugins/${project.artifactId}/add-link.action?key=$generalUtil.urlEncode($helper.spaceKey)</link>
     <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition"/>
 </web-item>

If your app needs to be backwards compatible with Confluence 5.x, you will need to add another web-item for the Space Admin area in the documentation theme:

atlassian-plugin.xml

1
2
<!-- Item in Space Admin (for Doc Theme) -->
<web-item key="space-admin-quick-link-manager-2" name="Quick Link Manager in Space Admin" section="system.space.admin/addons" weight="100">
    <label key="space.admin.quick.link.manager" />
    <link linkId="space-admin-quick-link-manager-id">/plugins/${project.artifactId}/add-link.action?key=$generalUtil.urlEncode($helper.spaceKey)</link>
    <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition" invert="true"/>
</web-item>

2. Declare an action

Next we need to declare an action in atlassian-plugin.xml:

atlassian-plugin.xml

1
2
<struts name="Example Actions" key="example-actions">
    <description>Examples of actions</description>
    <package name="space-links-struts-package" extends="default" namespace="/plugins/unique/value">
        <default-interceptor-ref name="validatingStack"/>
 
        <action name="add-link" class="com.atlassian.examples.MyAction" method="doCreate">
            <result name="input" type="velocity">/templates/add-link-action.vm</result>
            <result name="success" type="velocity">/templates/add-link-action.vm</result>
        </action>
 
        <action name="mixed" class="com.atlassian.examples.MixedAction" method="doDefault">
            <result name="input" type="velocity">/templates/mixed-space-screen.vm</result>
        </action>
    </package>
</struts>

The action must extend SpaceAdminAction. This will check whether the user has permissions for the space:

1
2
public class MyAdminAction extends SpaceAdminAction
{
    @Override
    public String doDefault()
    {
        return INPUT;
    }
}

3. Configure decorator

The template needs to use space.admin decorator. You also need to provide the key of web-item to highlight in space admin/tools.

1
2
<html>
    <head>
        <title>$action.getText("add.link.action")</title>
        <meta name="decorator" content="space.admin"/>
        <!-- Key of the web-item to highlight in Space Admin, discard this if you don't support pre-5.0 themes -->
        <content tag="space.admin.selected">space-admin-quick-link-manager-2</content>
        <!-- Key of the web-item to highlight in Space Tools -->
        <content tag="space.admin.tools.selected">space-admin-quick-link-manager</content>
    </head>
    <body>
         <div class="pagecontent">
              <p>${message}</p>
         </div>
    </body>
</html>

Here's the result:

Create a Space Tools screen using a servlet

1. Create a web-item

First we need to add a web-item for Space Tools in atlassian-plugin.xml. If the screen should only be visible to space admins, you will need to use a Condition to check this.

atlassian-plugin.xml

1
2
<!-- Item in Space Tools -->
<web-item key="space-admin-view-links" name="View links in Space Admin" section="system.space.tools/addons" weight="101">
    <label key="space.admin.view.links" />
    <link linkId="space-admin-view-links-id">/plugins/servlet/spacelinks?spaceId=$space.id</link>
    <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition"/>
</web-item>

If your app needs to be backwards compatible with Confluence 5.x, you will need to add another web-item for the Space Admin area in the documentation theme

atlassian-plugin.xml

1
2
<!-- Item in Space Admin (for Doc Theme) -->
<web-item key="space-admin-view-links-2" name="View links in Space Admin" section="system.space.admin/addons" weight="101">
    <label key="space.admin.view.links" />
    <link linkId="space-admin-view-links-id">/plugins/servlet/spacelinks?spaceId=$space.id</link>
    <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition" invert="true"/>
</web-item>

2. Add a servlet

Next we need to add a servlet in atlassian-plugin.xml.

atlassian-plugin.xml

1
2
<servlet name="View links Servlet" key="viewlink-servlet" class="com.atlassian.examples.ViewLinkServlet">
    <description>Display links.</description>
    <url-pattern>/spacelinks</url-pattern>
</servlet>

The servlet needs to import some Confluence components:

pom.xml

1
2
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.sal</groupId>
    <artifactId>sal-api</artifactId>
    <version>4.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.atlassian.templaterenderer</groupId>
    <artifactId>atlassian-template-renderer-api</artifactId>
    <version>4.0.0</version>
    <scope>provided</scope>
</dependency>

atlassian-plugin.xml

1
2
<component-import key="userManager" interface="com.atlassian.sal.api.user.UserManager"/>
<component-import key="userAccessor" interface="com.atlassian.confluence.user.UserAccessor"/>
<component-import key="loginUriProvider" interface="com.atlassian.sal.api.auth.LoginUriProvider"/>
<component-import key="spaceManager" interface="com.atlassian.confluence.spaces.SpaceManager"/>
<component-import key="templateRenderer" interface="com.atlassian.templaterenderer.TemplateRenderer"/>

ViewLinkServlet.java

1
2
package com.atlassian.examples;
 
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.ConfluenceUser;
import com.atlassian.confluence.user.UserAccessor;
import com.atlassian.sal.api.auth.LoginUriProvider;
import com.atlassian.sal.api.user.UserKey;
import com.atlassian.sal.api.user.UserManager;
import com.atlassian.templaterenderer.TemplateRenderer;
import com.google.common.collect.ImmutableMap;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
 
import static java.util.Objects.requireNonNull;
 
public class ViewLinkServlet extends HttpServlet {
    private static final String SPACE_ID_KEY = "spaceId";
    private final UserManager userManager;
    private final UserAccessor userAccessor;
    private final LoginUriProvider loginUriProvider;
    private final SpaceManager spaceManager;
    private final TemplateRenderer templateRenderer;
 
    public ViewLinkServlet(UserManager userManager, UserAccessor userAccessor, LoginUriProvider loginUriProvider,
                           SpaceManager spaceManager, TemplateRenderer templateRenderer) {
        this.userManager = requireNonNull(userManager);
        this.userAccessor = requireNonNull(userAccessor);
        this.loginUriProvider = requireNonNull(loginUriProvider);
        this.spaceManager = requireNonNull(spaceManager);
        this.templateRenderer = requireNonNull(templateRenderer);
    }
 
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Find current user
        UserKey userKey = userManager.getRemoteUserKey(request);
        ConfluenceUser user = userAccessor.getExistingUserByKey(userKey);
        if (userKey == null || user == null) {
            redirectToLogin(request, response);
            return;
        }
 
        // Find space in the request
        Space space = null;
        String spaceIdString = request.getParameter(SPACE_ID_KEY);
        if (spaceIdString != null) {
            try {
                long spaceId = Long.parseLong(spaceIdString);
                space = spaceManager.getSpace(spaceId);
            } catch (NumberFormatException nfe) {
                // log a warning if needed
            }
        }
 
        if (space == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
 
        response.setContentType("text/html;charset=utf-8");
        templateRenderer.render("/templates/view-link-action.vm",
                ImmutableMap.of("spaceId", space.getId()),
                response.getWriter());
 
    }
 
    private void redirectToLogin(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.sendRedirect(loginUriProvider.getLoginUri(getUri(request)).toASCIIString());
    }
 
    private URI getUri(HttpServletRequest request) {
        StringBuffer builder = request.getRequestURL();
        if (request.getQueryString() != null) {
            builder.append("?");
            builder.append(request.getQueryString());
        }
        return URI.create(builder.toString());
    }
}

3. Create a template for the servlet

Now, prepare the template for this servlet. The template needs to specify space.admin as the decorator. You also need to provide the spaceId of the current space and the key of the web-item to highlight in space admin/tools.

view-link-action.vm

1
2
<html>
    <head>
        <title>View links</title>
        <meta name="decorator" content="space.admin"/>
        <meta name="spaceid" content="$spaceId"/>
        <!-- Key of the web-item to highlight in Space Admin -->
        <content tag="space.admin.selected">space-admin-view-links-2</content>
        <!-- Key of the web-item to highlight in Space Tools -->
        <content tag="space.admin.tools.selected">space-admin-view-links/content>
    </head>
 
    <body>
    <div class="pagecontent">
        <p>Your links should appear here</p>
    </div>
    </body>
</html>

Here is the result:

Create a Space Tools screen in Confluence 7.4 and earlier

The space.admin decorator is only available in Confluence 7.5 and later. For earlier versions, you'll need to use Velocity and apply the 2 decorators in the way we've described below.

1. Create a web-item

You need two web-items, for Space Tools (5.x and later themes) and Space Admin (pre-5.0 themes). If the screen should only be visible to space admins, you will need to use a Condition to check this.

atlassian-plugin.xml

1
2
   <!-- Item in Space Tools -->
<web-item key="space-admin-quick-link-manager" name="Quick Link Manager in Space Admin" section="system.space.tools/addons" weight="100">
    <label key="space.admin.quick.link.manager" />
    <link linkId="space-admin-quick-link-manager-id">/plugins/${project.artifactId}/add-link.action?key=$generalUtil.urlEncode($helper.spaceKey)</link>
    <conditions type="AND">
        <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpacePermissionCondition">
            <param name="permission">administer</param>
        </condition>
                <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition"/>
    </conditions>
</web-item>
<!-- Item in Space Admin (for Doc Theme) -->
<web-item key="space-admin-quick-link-manager-2" name="Quick Link Manager in Space Admin" section="system.space.admin/addons" weight="100">
    <label key="space.admin.quick.link.manager" />
    <link linkId="space-admin-quick-link-manager-id">/plugins/${project.artifactId}/add-link.action?key=$generalUtil.urlEncode($helper.spaceKey)</link>
    <conditions type="AND">
        <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpacePermissionCondition">
            <param name="permission">administer</param>
        </condition>
                <condition class="com.atlassian.confluence.plugin.descriptor.web.conditions.SpaceSidebarCondition" invert="true"/>
    </conditions>
</web-item>

1. Declare an action

For this example, we will also declare an action:

atlassian-plugin.xml

1
2
<struts name="Example Actions" key="example-actions">
    <description>Examples of actions</description>
    <package name="space-links-struts-package" extends="default" namespace="/plugins/unique/value">
        <default-interceptor-ref name="validatingStack"/>
        <action name="add-link" class="com.atlassian.examples.MyAdminAction">
            <result name="input" type="velocity">/templates/add-link-action.vm</result>
            <result name="success" type="velocity">/templates/add-link-action.vm</result>
        </action>
    </package>
</struts>

The action must extend SpaceAdminAction, this will check whether the user has permissions for the space:

1
2
public class MyAdminAction extends SpaceAdminAction
{
    @Override
    public String doDefault()
    {
        return INPUT;
    }
}

3. Apply the decorators

The template needs two decorators:

add-link-action.vm

1
2
<html>
    <head>
        <title>$action.getText("add.link.action")</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 Admin
            #decoratorParam ("selection" "quick-link-manager-2") 
            ## Key of the web-item to highlight in Space Tools
            #decoratorParam ("selectedSpaceToolsWebItem" "quick-link-manager") 
            #decoratorParam ("helper" $action.helper)
            <body>
                <div class="pagecontent">
                    <p>${message}</p>
                </div>
            </body>
        #end
    #end
</html>

Context:

  • spaceadminpanel: Valid for Space Tools (for the new 5.0 themes) and Space Admin (in pre-5.0 themes).
  • spacecontentpanel: Valid for Space Tools (new 5.0 themes) and Space Operations (pre-5.0 themes).
  • spacetoolspanels: Valid for Space Tools only.

Here is the result:

The example above is also compatible with the Doc Theme and other pre-5.0 themes:

Web UI Modules

Rate this page: