ContentService and SpaceService in Confluence for Datacenter

Once upon a time, things were simple.  If you wanted to retrieve a page using the Confluence Java API, you simply called getPage().    Fetching Spaces was similarly easy, and intuitive.

Those days are over.  The methods are deprecated. Instead, we now need to use SpaceService and ContentService to manage spaces and content, respectively.  Let’s take a look at some examples of how a task would have been accomplished with the PageManager and SpaceManager, and compare that to how those tasks would be accomplished today.

This the code required to return all page content for all spaces, using PageManager and Spacemanager:
import com.atlassian.confluence.pages.PageManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.spaces.SpaceManager

def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)

def spaces = spaceManager.getAllSpaces()

spaces.each { space ->
    def pagesInSpace = pageManager.getPages(space, true)
    
    pagesInSpace.each { page ->
        log.warn(page.getBodyAsString())
    }
}

 

Here’s the same code, using the SpaceService and ContentService classes:

import com.atlassian.confluence.api.model.Expansions
import com.atlassian.confluence.api.model.content.ContentRepresentation
import com.atlassian.confluence.api.model.content.ContentBody
import com.atlassian.confluence.api.model.content.Content
import com.atlassian.confluence.api.model.content.Space
import com.atlassian.confluence.api.model.Expansion
import com.atlassian.confluence.api.model.pagination.PageResponse
import com.atlassian.confluence.api.service.content.SpaceService
import com.atlassian.confluence.api.service.content.ContentService
import com.atlassian.confluence.api.model.content.ContentType
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.atlassian.confluence.api.model.pagination.SimplePageRequest


def contentService = ScriptRunnerImpl.getPluginComponent(ContentService)
def spaceService = ScriptRunnerImpl.getPluginComponent(SpaceService)
 
SimplePageRequest pageRequest = new SimplePageRequest(0, 10)
 
PageResponse < Space > spaceResults = spaceService.find(new Expansion('name')).fetchMany(new SimplePageRequest(0, 10))
 
List < Space > spaces = spaceResults.getResults()
 
spaces.each {space ->
 
  def pageResult = contentService.find(new Expansion(Content.Expansions.BODY, new Expansions(new Expansion("storage"))))
    .withSpace(space)
    .fetchMany(ContentType.PAGE, pageRequest)
  List pages = pageResult.getResults()
 
  pages.each {page ->
 
    ContentBody body = page.getBody().get(ContentRepresentation.STORAGE);
    log.warn(body.getValue())
 
  }
}
 

 After the very long list of imports, and the declaration of the space and content services, we create a SimplePageRequest object. The parameters that this takes, 0 and 10, are the start and the limit of pagination, respectively. That is, the request starts at index 0, and increments by 10.

Next we’re creating an object that stores the results of what this line is trying to achieve.  The object is of type PageResponse.  Essentially this is the other half of the pagination mechanism; it’s the part that tells the script when pagination has finished.   This object holds the request of the SpaceService, which queries the system for spaces that match the criteria.  As we haven’t provided it with any limitations or filters, it’ll return all spaces. 
If we wanted to add some filters or limitations to the search, we’d use .with():

 

This provides methods by which search criteria can be included with the find() statement.

After that runs, we have a list of spaces within the instance that match our criteria.    We iterate through those spaces with a closure, and for each space we use the ContentService to retrieve content relating to that space.  Worth noting is that you don’t need to start with the SpaceService; if you have a single space object, or some other criteria, you could use that as a .with() parameter attached to the ContentService.

The structure of the .find() statement is such that we feed must tell it which details we’d like to expand on; Atlassian calls these expansions.  In effect, we tell the .find() statement which details about the content we’d actually like to know about.   If we don’t tell the statement which expansions we’d like to use, the information will likely not be returned by the statement.

That’s a very important aspect of this new paradigm: expansions aren’t a handy feature, they are foun>dational to the search for content that you are conducting.  In this instance, we’ve expanded on the body of the pages being returned, and within each of those expansions is another expansion of the storage.  Remember, page content in Confluence is store in body.storage.

Finally, and I must stress this, we need to call for ContentRepresentation.STORAGE if we want to actually see the contents of the body.  This information was incredibly difficult to find.   You can call page.getBody() by itself as much as you like, but it’ll return an empty map.

I’m certain there are aspects of this service model that I’ve not touched on or encountered yet. If you have any suggestions for other people looking to make use of these services, please leave a comment.

Leave a Reply

Your email address will not be published. Required fields are marked *