Active Object save() method doesn't persist data under certain conditions

Dmytro Royenko March 22, 2019

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:

@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

 

2 answers

1 accepted

3 votes
Answer accepted
Dmytro Royenko March 26, 2019

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.

Alex Medved _ConfiForms_
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.
November 30, 2020

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

0 votes
Dmytro Royenko March 25, 2019

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.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events