Active Object save() method doesn't persist data under certain conditions
Asked by Dmytro Royenko:

Hi,

I have developed RefApp plugin which will be used in Jira, Bitbucket and Confluence products. One of the functionality of this plugin will be to store auth data after successful automated oAuth dance for each user in the local h2 db. Storing this data is performed by this method:

```
@Scanned
@Named
@Component
public class ClientAtlasUserServiceImpl implements ClientAtlasUserService {

    private final ActiveObjects activeObjects;

    @Autowired
    public ClientAtlasUserServiceImpl(@ComponentImport ActiveObjects activeObjects) {
        this.activeObjects = activeObjects;
    }

    @Override
    public ClientAtlasUser add(Map<String, String> properties) {

        List<ClientAtlasUser> usersByClientId = getUserByClientId(properties.get(CLIENT_ID));
        final ClientAtlasUser user = (usersByClientId.isEmpty()) 
                         ? activeObjects.create(ClientAtlasUser.class) 
                         : retrieveFirstClientAtlasUser(usersByClientId);

        for (Map.Entry entry : properties.entrySet()) {
            if (ACCESS_TOKEN.equals(entry.getKey()))
                user.setAtlasAccessToken((String) entry.getValue());
            else if (REQUEST_TOKEN.equals(entry.getKey()))
                user.setAtlasRequestToken((String) entry.getValue());
            else if (SECRET.equals(entry.getKey()))
                user.setAtlasSecret((String) entry.getValue());
            else if (CLIENT_ID.equals(entry.getKey()))
                user.setClientUserId((String) entry.getValue());
            else
                LOG.debug("Wrong property name for user entity");
        }
        user.save();
    }
```

This method is implemented from the interface:

```
@@Transactional
public interface ClientAtlasUserService {
    ClientAtlasUser add(Map<String, String> properties);

    List<ClientAtlasUser> all();

    List<ClientAtlasUser> getUserByClientId(String clientId);
```

And the Entity interface is:

```
public interface ClientAtlasUser extends Entity {

    String getClientUserId();

    void setClientUserId(String clientUserId);

    String getAtlasSecret();

    void setAtlasSecret(String atlasSecret);

    String getAtlasRequestToken();

    void setAtlasRequestToken(String atlasRequestToken);

    String getAtlasAccessToken();

    void setAtlasAccessToken(String atlasAccessToken);
```

Dependency in the POM file:

```
<dependency>
    <groupId>com.atlassian.activeobjects</groupId>
    <artifactId>activeobjects-plugin</artifactId>
    <version>3.0.0</version>
    <scope>provided</scope>
</dependency>
```

Module in the atlassian-plugin.xml:

```
<ao key="ao-module">
    <description>The module configuring the Active Objects service used by this plugin</description>
    <entity>com.microsoft.client.ao.ClientAtlasUser</entity>
</ao>
```

So the question is, when I'm adding new ClientAtlasUser entity through the basic UI modified from the [example](https://developer.atlassian.com/server/framework/atlassian-sdk/getting-started-with-active-objects/):

```
@Scanned
public class ClientAtlasUserServlet extends HttpServlet {

    private final ClientAtlasUserService userService;

    public ClientAtlasUserServlet(ClientAtlasUserService userService) {
        this.userService = userService;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        try {
            final PrintWriter w = resp.getWriter();
            w.write("<h1>Client-Atlas Mapping</h1>");
            w.write("<form method=\"post\">");
            w.write("ClientUserID:<br>");
            w.write("<input type=\"text\" name=\"ClientUserId\" size=\"25\"/><br>");
            w.write("AtlasSecret:<br>");
            w.write("<input type=\"text\" name=\"AtlasSecret\" size=\"25\"/><br>");
            w.write("AtlasRequestToken:<br>");
            w.write("<input type=\"text\" name=\"AtlasRequestToken\" size=\"25\"/><br>");
            w.write("AtlasAccessToken:<br>");
            w.write("<input type=\"text\" name=\"AtlasAccessToken\" size=\"25\"/><br>");
            w.write("  ");
            w.write("<input type=\"submit\" name=\"submit\" value=\"Add\"/>");
            w.write("</form>");

            w.write("<table>");
            w.write("<tr>");
            w.write("<th>ClientUserId</th>");
            w.write("<th>AtlasSecret</th>");
            w.write("<th>AtlasRequestToken</th>");
            w.write("<th>AtlasAccessToken</th>");
            w.write("</tr>");
            for (ClientAtlasUser user : userService.all()) {
                w.write("<tr>");
                w.printf("<td> %s </td>", user.getClientUserId());
                w.printf("<td> %s </td>", user.getAtlasSecret());
                w.printf("<td> %s </td>", user.getAtlasRequestToken());
                w.printf("<td> %s </td>", user.getAtlasAccessToken());
                w.write("</tr>");
            }

            w.write("</table>");
            w.write("<script language='javascript'>document.forms[0].elements[0].focus();</script>");

            w.close();
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        try {
            Map<String, String> properties = new HashMap<>();
            properties.put(CLIENT_ID, req.getParameter("ClientUserId"));
            properties.put(SECRET, req.getParameter("AtlasSecret"));
            properties.put(REQUEST_TOKEN, req.getParameter("AtlasRequestToken"));
            properties.put(ACCESS_TOKEN, req.getParameter("AtlasAccessToken"));
            userService.add(properties);

            resp.sendRedirect(req.getContextPath() + "/plugins/servlet/user/mapping");
        } catch (IOException e) {
            LOG.error(e.getMessage());
        }
    }
}
```

It persists to the db and I can verify it exists on this ui. But when I call userService.add() method from my code, after I have successfully obtained access token with the same set of Map\<String, String\> properties arguments it doesn't save data to db. Also I don't have this problem when running this RefApp plugin in Jira version 7.12.0, or Bitbucket version 5.15.0 only with Confluence (version 6.12.0 tested only).  
Also when I'm trying to modify user which I have previously created through ui passing parameters with existing CLIENT_ID through the code (not ui), I'm getting correct entity of user from db through:

```
getUserByClientId(properties.get(CLIENT_ID))
```

then passing new values to this user entity in ClientAtlasUserServiceImpl.add() method and after save I verify that this user hasn't changed.

Thanks advance for your help


Answers:
author: Dmytro Royenko
body: After deeper investigation it turned out that all interaction with ActiveObjects executed in Confluence need to be performed in Transaction. So after rewriting my add method looks like this:

```
@Override
public void add(Map<String, String> properties) {

    activeObjects.executeInTransaction((TransactionCallback<Object>) () -> {
        List<TeamsAtlasUser> usersByTeamsId = getUserByTeamsId(properties.get(TEAMS_ID));
        final TeamsAtlasUser user = (usersByTeamsId.isEmpty()) ? activeObjects.create(TeamsAtlasUser.class)
                : retrieveFirstTeamsAtlasUser(usersByTeamsId);

        for (Map.Entry entry : properties.entrySet()) {
            if (ACCESS_TOKEN.equals(entry.getKey()))
                user.setAtlasAccessToken((String) entry.getValue());
            else if (REQUEST_TOKEN.equals(entry.getKey()))
                user.setAtlasRequestToken((String) entry.getValue());
            else if (SECRET.equals(entry.getKey()))
                user.setAtlasSecret((String) entry.getValue());
            else if (TEAMS_ID.equals(entry.getKey()))
                user.setMsTeamsUserId((String) entry.getValue());
            else
                LOG.debug("Wrong property name for user entity");
        }
        user.save();
        return user;
    });
}
```

And now data is persisted just fine. Also this approach is valid for Jira and Bitbucket.

comments: author: Alex Medved _ConfiForms_
body: Thank you for sharing this! I have been struggling with this for 3+ hours...

Just a crazy stuff! (especially when you see the code is working when it's called from an xwork action but does not work when is executed from a servlet).

The most frustrating part is that you can actually see the update been executed (SQL is printed - <https://developer.atlassian.com/server/framework/atlassian-sdk/enabling-sql-logging/>)... but no actual update is happening


author: Dmytro Royenko
body: I assume this is something due to base URL which I use for access. When I run this refapp on confluence with: atlas-debug --product confluence --version 6.12.0 it starts as usual on localhost:1990/confluence. Then when I enter its Dashboard I'm getting message "**Your URL doesn't match** Confluence's base url is set to http://macmini8383:1990/confluence but you are accessing Confluence from http://localhost:1990/confluence"  
Also I have noticed that if I create new record with active object through UI accessing it with localhost:1990 I'm not able to persist new entity to ao just as I wasn't able to do this from my code. But if I accessing this UI from macmini8383:1990 persisting works just fine. I also try to change basic URL in settings from macmini8383 to localhost just as it's proposed, behavior hasn't changed. After all I still not able to persist record from my plugin code workflow.


Tags: confluence-server

server