Can't create a new confluence page through scheduled job programmatically
Asked by Johan Jonsson Nilsson:

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?


Answers:
author: Johan Jonsson Nilsson
body: 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!


author: Aleksandr Zuevich
body: Hi again [@Johan Jonsson Nilsson](/t5/user/viewprofilepage/user-id/684377) ,

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](https://developer.atlassian.com/server/confluence/spring-ioc-in-confluence) 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/>

comments: author: Aleksandr Zuevich
body: Hi [@Johan Jonsson Nilsson](/t5/user/viewprofilepage/user-id/684377) ,

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?


author: Johan Jonsson Nilsson
body: Hi!

Absolutely, please see my accepted answer.


Tags: app

atlassian-connect

atlassian-sdk

confluence-server

server