Can't create a new confluence page through scheduled job programmatically

Johan Jonsson Nilsson October 29, 2019

Hi!

I have now faced this issue for a couple of days now, and I can't seem to be able to fix it. I have tried to do the following code:

public boolean convertMailToPageProgrammatically(Message m, String parentPageTitle, String title, String spaceKey) {
try {
log.warn("convertMailToPage for message " + m.getSubject());
SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager.class);
PageManager pageManager = ComponentLocator.getComponent(PageManager.class);
ConfluenceIndexer confluenceIndexer = ComponentLocator.getComponent(ConfluenceIndexer.class);
Space spaceToUse = spaceManager.getSpace(spaceKey);

log.warn("Page: " + pageManager.getPage("SpaceKey", "New Page 1337").getTitle());

Page page = new Page();
Page parentPage = pageManager.getPage(spaceKey, parentPageTitle);
page.setSpace(spaceToUse);
page.setTitle(title);
page.setBodyAsString(getTextFromMessage(m));
page.setVersion(1);
page.setParentPage(parentPage);
log.warn("now we are here");
pageManager.saveContentEntity(page, null);
parentPage.addChild(page);
confluenceIndexer.reIndex(page);
return true;
} catch (MessagingException | IOException e) {
log.error("ERROR! " + e.getMessage());
return false;
}

}

This code has worked in a lot of my other plugins. However, the difference now is that I'm trying to run this through a Confluence Scheduled Job, which itself seems to run just fine. You can see the code below:

package net.teliacompany.diva.confluence.purgingbackend.job;

import javax.mail.MessagingException;

import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.scheduler.JobRunner;
import com.atlassian.scheduler.JobRunnerRequest;
import com.atlassian.scheduler.JobRunnerResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import net.teliacompany.diva.confluence.purgingbackend.mail.MailUtilities;
import net.teliacompany.diva.confluence.purgingbackend.utilities.PARUtilities;

@Component
@Scanned
public class PurgingAndRetentionJobv2 implements JobRunner {

private static final Logger log = LoggerFactory.getLogger(PurgingAndRetentionJobv2.class);
private final PARUtilities parUti = new PARUtilities();
private final MailUtilities mailUti = new MailUtilities();

@Override
public JobRunnerResponse runJob(JobRunnerRequest request) {
try {
int noOfMails = mailUti.checkIfThereAnyNewAvailableMails();
if (noOfMails > 0) {
log.warn("There was " + noOfMails + " mails to process");
return JobRunnerResponse.success("There was " + parUti.processMails() + " new mail, job complete!");
} else {
return JobRunnerResponse.success("There was no new mail, job complete!");
}
} catch (MessagingException ex) {
return JobRunnerResponse.failed("Job Failed!");
}
}
}

However, as soon as I try to execute, it runs fine (it can get pages just fine when using the PageManager). But when it it arrives at saving the actual page, I get the following error:

2019-10-29 09:52:30,696 WARN [Caesium-1-4] [confluence.impl.hibernate.ConfluenceHibernateTransactionManager] doRollback Performing rollback. Transactions:
->[com.atlassian.confluence.pages.DefaultPageManager.saveContentEntity]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT (Session #247582940)
2019-10-29 09:52:30,697 ERROR [Caesium-1-4] [confluence.purgingbackend.utilities.PARUtilities] processMails ERROR!
2019-10-29 09:52:30,698 ERROR [Caesium-1-4] [confluence.purgingbackend.utilities.PARUtilities] processMails A different object with the same identifier value was already associated with the session : [com.atlassian.confluence.spaces.Space#21594119]; nested exception is org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.atlassian.confluence.spaces.Space#21594119]
2019-10-29 09:52:30,698 ERROR [Caesium-1-4] [confluence.purgingbackend.utilities.PARUtilities] processMails ERROR:___ org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:265)

 I have tried using both Atlassian Spring Scanner version 1 and 2 to no avail, I always end up with the same error no matter what I do. What am I doing wrong?

2 answers

1 accepted

1 vote
Answer accepted
Johan Jonsson Nilsson January 3, 2020

Here's the final solution of the code, if anyone has a need for it. What you have to do is that you need to create a transaction template for the page creation when doing it during a scheduled job. Also, it's important to note that the code has somewhat changed from the answer above, but this should help anyone that wants to create a page (or perhaps other things) during a schedule job.

Component imports and constructor:

@ComponentImport
private final SpaceManager spaceManager;
@ComponentImport
private final PageManager pageManager;
@ComponentImport
private final ConfluenceIndexer confluenceIndexer;
@ComponentImport
private final TransactionTemplate transactionTemplate;
@ComponentImport
private final XhtmlContent xhtmlContent;
@ComponentImport
private final UserAccessor userAccessor;

public Utilities(SpaceManager spaceManager, PageManager pageManager, ConfluenceIndexer confluenceIndexer, TransactionTemplate transactionTemplate, XhtmlContent xhtmlContent, UserAccessor userAccessor) {
this.spaceManager = spaceManager;
this.pageManager = pageManager;
this.confluenceIndexer = confluenceIndexer;
this.transactionTemplate = transactionTemplate;
this.xhtmlContent = xhtmlContent;
this.userAccessor = userAccessor;
}

createPageProgrammatically method:

public boolean createPageProgrammatically(String spaceKey, String title, String parentPageTitle) {
transactionFlag = false;
transactionTemplate.execute(() -> {
try {
Date dateToUse = new Date();
Page page = new Page();
Page parentPage = pageManager.getPage(spaceKey, parentPageTitle);
Space space = spaceManager.getSpace(spaceKey);

ConfluenceUser user = null;

if (userAccessor.exists("Support")) user = userAccessor.getUserByName("Support");

page.setCreator(user);
page.setLastModifier(user);
page.setSpace(space);
page.setTitle(title);
page.setCreationDate(dateToUse);
page.setLastModificationDate(dateToUse);
page.setBodyAsString("");
page.setVersion(1);
page.setParentPage(parentPage);
pageManager.updatePageInAncestorCollections(page, parentPage);
pageManager.saveContentEntity(page, new DefaultSaveContext.Builder().build());
parentPage.addChild(page);
confluenceIndexer.reIndex(page);
transactionFlag = true;
} catch (Exception e) {
log.error("ERROR! Page couldn't be created! Message: " + e.getMessage());
}
return null;
});
return transactionFlag;
}

Also, please note that I also received the same issue as above when passing a space object to this code. Turns out that you need to get all objects, such as pages or spaces, in the actual transaction, or else it will fail with a similar error to my opening post. If you follow the way that I did in this answer, you should be fine.

Also, I encountered an issue with an ancestors table error when running the code like this. Adding the "pageManager.updatePageInAncestorCollections(page, parentPage);" line at that exact spot fixed it for me, i.e. adding it after "page.setParentPage(parentPage);" and before "pageManager.saveContentEntity(page, new DefaultSaveContext.Builder().build());"

Hope it helps!

0 votes
Aleksandr Zuevich
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 29, 2019

Hi again @Johan Jonsson Nilsson ,

Confluence uses the "Session in view" pattern for managing Hibernate sessions. The SessionInViewFilter opens a Hibernate session which is then available for the entire web request.

As you mentioned before the difference now is that I'm trying to run this through a Confluence Scheduled Job which means that there is no a Hibernate session to work with. I suppose you need to create and manage a session manually:

HibernateTemplate template = new HibernateTemplate(sessionFactory, true);
template.execute(new HibernateCallback()
{
    @Override
    public Object doInHibernate(Session session) throws HibernateException, SQLException
    {
        // ... execute database-related code ...
        
        return null;
    }
});

The type of the sessionFactory field is net.sf.hibernate.SessionFactory. You can get this injected by Spring into your component.

This code will create a new session if one is not already bound to the thread, execute the callback code, then close the session and release the database connection back to the pool.

https://developer.atlassian.com/server/confluence/hibernate-sessions-and-transaction-management-guidelines/

Aleksandr Zuevich
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 3, 2020

Hi @Johan Jonsson Nilsson ,

Please let us know if the above answer helps you and if it's not too much trouble could you share your final solution for anybody future needs?

Johan Jonsson Nilsson January 3, 2020

Hi!

Absolutely, please see my accepted answer.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events