Previous: Synchronizing User Information with Local Data (User Information Chapter)

In the “User Information Chapter”, we manipulated User information which was only enough to log in to an account, using the System Setting API of Questetra BPM Suite. In this post, I will introduce an example program for manipulating the information of the User’s affiliation to Organizations registered in Questetra BPM Suite. As with user information, prepare a TSV file that indicates which Organization each User belongs to, and synchronizes the registered User information on Questetra BPM Suite accordingly. In this example, the following data is assumed in a TSV file.

questetra@gmail.com00 Questetra Inc.!
questetra+Galapagos@gmail.com20 Sales Department!
questetra+Oahu@gmail.com20 Sales Department
questetra+Sumatera@gmail.com10 Management Department!
questetra+Canarias@gmail.com10 Management Department 30 Development Department

The format is that registering user’s email address first, the organization to which the user belongs follows with a tab separator. In the above example, it represents that the user with the email address of “questetra+Canarias@gmail.com” belongs to both of “10 Management Department” and “30 Development Department”. In addition, as you see “20 Sales Department!” of questetra+Galapagos@gmail.com, an “!” attached at the end of an Organization name indicates the user is the leader of the Organization. The goal of this article is to match the contents of a TSV file in such format with the user information registered in Questetra.

As with user information, you need a class for comparing the TSV file with the registered information on Queqstetra BPM Suite to identify the difference and a class for manipulating the information of User’s affiliation to Organizations on Questetra BPM Suite using its API. Let’s implement the former as SyncMembershipWithTSV class and the latter as the MembershipDataManager class.

MembershipDataManager class

To manipulate the information of User’s affiliation to Organizations, we will use the MembershipApi class (a class that manipulates information of User’s affiliation to Organizations) and QgroupApi class (a class that manipulates Organization information) in the com.questetra.bpms.client.swagger, the library of Questetra. Also, we will use UserDataManager class which we implemented in the “User Information Chapter” to check the user ID.

Basic Authentication (Constructor)

import com.questetra.bpms.client.swagger.model.*;
import com.questetra.bpms.client.swagger.ApiClient;
import com.questetra.bpms.client.swagger.ApiException;
import com.questetra.bpms.client.swagger.Configuration;
import com.questetra.bpms.client.swagger.auth.*;
import com.questetra.bpms.client.swagger.api.MembershipApi;
import com.questetra.bpms.client.swagger.api.QgroupApi;

import java.util.HashMap;


/**
 * List and manipulate the affiliation of Users.
 */
class MembershipDataManager {
    private final MembershipApi apiInstance;
    private final HashMap<String, QuserWithPrimaryQgroup> userMap = new HashMap<>();
    private final HashMap<String, Qgroup> groupMap = new HashMap<>();

    public MembershipDataManager() {
        ApiClient defaultClient = Configuration.getDefaultApiClient();
        // Set URL
        defaultClient.setBasePath("https://example.questetra.net/");

        // Basic Authentication
        HttpBasicAuth basic = (HttpBasicAuth) defaultClient.getAuthentication("basic");
        basic.setUsername("{EMAIL ADDRESS}");
        basic.setPassword("{PASSWORD for Basic Authentication}");

        this.apiInstance = new MembershipApi();
    }

...

The codes for Basic authentication by constructor are the same as the UserDataManager class. Note that we are importing MembershipApi and QgroupApi since we will deal with Organizational information this time.

Also, as we will implement a method for acquiring user objects and organization objects from Questetra BPM Suite in the next section, make a declaration of member variables, userMap and groupMap to be used there. These are HashMap objects for storing information on users and organizations once searched.

Methods for searching Users/Organizations

     ...

    /**
     * Search for Organizations (Qgroup) by Organization name
     * Throw ApiException if the Organization specified by the name doesn't exist on Questetra BPM Suite
     * @param name: Organization name
     * @return Qgroup: Organization object that has found by search
     */
    private Qgroup findGroup(String name) throws ApiException {
        // Organizations that have already been found are added to HashMap
        if (this.groupMap.containsKey(name)) {
            return this.groupMap.get(name);
        }
        QgroupApi groupApiInstance = new QgroupApi();
        Qgroup result = groupApiInstance.find(null, name).getQgroup();
        // HashMapに追加
        this.groupMap.put(name, result);
        return result;
    }

    /**
     * Search for a User (QuserWithPrimaryQgroup) by email address.
     * Throw ApiException if the User with specified email address doesn't exist on Questetra BPM Suite
     * @param email: Email address
     * @return QuserWithPrimaryQgroup: Found User object
     */
    private QuserWithPrimaryQgroup findUser(String email) throws ApiException {
        // Users that have already been found are added to HashMap
        if (this.userMap.containsKey(email)) {
            return this.userMap.get(email);
        }
        UserDataManager userManager = new UserDataManager();
        QuserWithPrimaryQgroup result = userManager.find(null, email).getQuser();
        // Add to HashMap
        this.userMap.put(email, result);
        return result;
    }

    ...

To use the methods of MembershipApi class, specification of Users or Organizations must be done with IDs. Therefore, you need to implement a method for searching Users object by email addresses written in the TSV file, and a method for searching Organizations by Organization names, beforehand. By invoking getId, an instance method, from these objects, User Id/Organization ID can be retrieved.

In the findUser method, the UserDataManager.find which we have implemented in “User Information Chapter” is used. The findUser method searches for User objects registered on Questetra BPM Suite by the email addresses passed to the argument and returns. Also, save the user object once retrieved in userMap, the member variable, of HashMap class. By using HashMap, you can retrieve the user object from here at the second and subsequent searches.

In the findGroup method, QgroupApi class which has been imported from API library is used. The QgroupApi class is a class for manipulating Organization information, and the QgroupApi.find method is a method that searches Organization objects. The arguments are id: an organization ID and name: an organization name, and only either of them is sufficient to specify. In this example, pass null to the argument id since Organization ID is unknown. Save the organization object in HashMap class as with the user object.

Method to list the Users affiliation to Organizations

    ...

    /**
     * Listing the User's affiliation to organization.
     * @param email: Email address of the User.
     * @return MembershipList: List of affiliated Organization.
     */
    public MembershipList findMemberships(String email) throws ApiException {
        // Search User ID of the specified email address
        Long userId = this.findUser(email).getId();
        MembershipList result = this.apiInstance.listByQuser(userId);
        return result;
    }

    ...

This is a method that returns the list of Organizations to which the specified user belongs. We will Implement this to compare Organizational affiliations in TSV files and registration information on Questetra BPM Suite.

In the MembershipApi class, listByQuser method which searches the list of affiliated organizations by user IDs is implemented. The argument of MembershipApi.listByQuser method is id: user ID. User IDs are retrieved by searching for User objects using aforementioned findUser method.

Method to add organizations to information of User’s affiliation to Organizations

    ...

    /**
     * Adding organization to information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organization name
     * @param isLeader: true for a leader, false for a staff
     * @return MembershipWrapper
     */
    public MembershipWrapper add(String email, String group,
                                 boolean isLeader) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        MembershipWrapper result;

        if (isLeader) {
            result = this.apiInstance.add(userId, groupId, "_leader");
            System.out.printf("Add: %s / %s, %s\n", email, group, "leader");
        } else {
            result = this.apiInstance.add(userId, groupId, null);
            System.out.printf("Add: %s / %s, %s\n", email, group, "staff");
        }

        return result;
    }

    ...

This is a method for adding organization to information of User’s affiliation to Organizations. When email: email address of the user, groups: organization name, and isLeader: a leader or not, are passed to arguments, Organizations will be added to affiliation of the target User. The argument, isLeader, is a boolean and if true indicates that the user is the leader of the organization.

The add method of MembershipApi class is used for the addition of organizations to User’s affiliation. There are three arguments which are, quserId: User ID, qgroupId: Organization ID, and role: status in the organization. Also, to retrieve the User ID and Organization ID, we use the methods, findUser and findGroup, which we implemented earlier.

Method to delete Organizations from information of User’s affiliation to Organizations

    ...

    /**
     * Deleting organizations from information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organization name.
     */
    public void delete(String email, String group) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        this.apiInstance.delete(userId, groupId);
        System.out.printf("Deleted: %s / %s\n", email, group);
    }

    ...

This is a method for deleting Organizations from information of User’s affiliation to Organizations. When the email: user’s email address and group: Organization name, are passed to the argument, those Organizations will be deleted from the affiliation of the user.

The delete method of MembershipApi class is used for the deletion of organization from information of User’s affiliation to Organizations. There are two arguments which are, quserId: User ID, qgroupId: Organization ID. As with the add method, User ID and Organization ID are retrieved using findUser and findGroup methods.

Method for updating information of User’s affiliation to Organizations

    ...

    /**
     * Updating information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organizational name
     * @param isLeader: true for a leader, false for a staff
     * @return MembershipWrapper
     */
    public MembershipWrapper update(String email, String group,
                                    boolean isLeader) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        MembershipWrapper result;

        // Change to Leader
        if (isLeader) {
            result = this.apiInstance.update(userId, groupId, "_leader");
            System.out.printf("Changed the role: %s / %s, → %s\n",
                              email, group, "leader");
        }
        // Change to Staff
        else {
            result = this.apiInstance.update(userId, groupId, null);
            System.out.printf("Changed the role: %s / %s, → %s\n",
                              email, group, "staff");
        }

        return result;
    }

}

This is a method for updating the status of Users in the Organization. When the email: user’s email address and group: Organization name and isLeader: a leader or not, are passed to the argument, the status of the target User will be updated. As with the add method, the isLeader indicates the User is a leader of the Organization if it is true.

The update method of MembershipApi class is used for the updating of information of User’s affiliation to Organizations. There are three arguments which are, quserId: User ID, qgroupId: Organization ID, and role: status in the organization. The way of usage is almost the same as the add method. You can use the MembershipApi.update method, for example, to change a user who has been a “Staff” to a “Leader” in an Organization.

SyncMembershipWithTSV class

I am going to describe processing for matching the difference in comparison of TSV file with registered information on Questetra BPM Suite. It will realize synchronization of information of User’s affiliation to Organizations between a local data and Questetra BPM Suite, by reading the information from a TSV file and Questetra BPM Suite respectively, determining which data to update, and executing the update via Web API.

main method

import com.questetra.bpms.client.swagger.ApiException;
import com.questetra.bpms.client.swagger.model.Membership;

import java.io.IOException;
import java.util.*;

import static java.util.stream.Collectors.toSet;

class SyncMembershipWithTSV {
    public static void main(String[] args) throws IOException, ApiException {
        // Reading TSV data
        List<String[]> data = TSVReader.readLines({PATH OF TSV FILE});
        // Processing one line by one.
        for (String[] user : data) {
            sync(user);
        }
    }
    ...

The basic operation is “Read TSV file -> Synchronize one line (one User) at a time”. TSVReader class is used for reading the TSV file as with the “User Information Chapter”. The imported data forms a List of String [], such as [Email Address, Organization 0, Organization 1, …]. Synchronization processing for one user is collected in the sync method.

sync (String[] user) method

With this method, synchronization of organizational affiliation information for one User is processed. The argument user is one line of data of the TSV file that has been read by TSVReader.readLines ().

Retrieving information of affiliation to Organizations of each User on Questetra BPM Suite

    ...

    /**
     * Synchronizing the information of affiliation to Organization of one User
     * @param user: One line of data read from TSV
     * @throws ApiException
     */
    private static void sync(String[] user) throws ApiException {
        MembershipDataManager manager = new MembershipDataManager();

        // Retrieve the organization to which the user currently belongs on Questetra BPM Suite
        Set<Membership> currentMembershipSet = new HashSet<>(
                            manager.findMemberships(user[0]).getMemberships());
        // Create a Set of "organizations to which the user belongs as a leader"
        Set<Membership> currentLeaderSet =
                            currentMembershipSet.stream()
                                      .filter(m -> Objects.equals(
                                              m.getRole(),Membership.RoleEnum.LEADER))
                                      .collect(toSet());

        Set<String> currentGroupSet = currentMembershipSet.stream()
                                                      .map(Membership::getQgroupName)
                                                      .collect(toSet());
        Set<String> currentLeaderGroupSet = currentLeaderSet.stream()
                                                      .map(Membership::getQgroupName)
                                                      .collect(toSet());

    ...

First, use the MembershipDataManager.findMemberships method to find out which organization the user belongs to on Questetra BPM Suite. Information on the organization to which the user currently belongs on Questetra BPM Suite can be obtained from a set of Membership objects. Create Set<String> type currentGroupSet by retrieving Organization names from Membership object, since where to change will be determined by comparing between Set<String> types later.

We will also prepare a set of organizations on Questetra BPM Suite to which the User belongs as a leader, to check if there is a change in the status of the User in an Organization. Create a CurrentLeaderGroupSet of Set<String> type by filtering the retrieved set of Membership object with “extract only organizations to which the Yser belongings as a leader” using Stream API, and retrieve Organization names from the remaining Membership object.

Retrieve information of Organizations to which each User belongs from TSV file

    ...

        // Retrieve information of Organizations to which a User belongs from TSV file
        Set<String> nextMembershipSet = new HashSet<>(Arrays.asList(user)
                                                            .subList(1, user.length));
        // Create a Set of "Organization to which the User belongs as a leader"
        Set<String> nextLeaderGroupSet =
                nextMembershipSet.stream()
                                 .filter(m -> m.endsWith("!"))
                                 .map(m -> m.substring(0, m.length()-1))
                                 .collect(toSet());
        Set<String> nextGroupSet = nextMembershipSet.stream()
                                    .map(m -> {
                                        if (m.endsWith("!"))
                                            return m.substring(0, m.length()-1);
                                        else return m;}).collect(toSet());

        sync(user[0], currentGroupSet, nextGroupSet,
                      currentLeaderGroupSet, nextLeaderGroupSet);
    }

    ...

In the same way, organizational affiliation information written in the TSV file is also collected. In this example, if an “!” is appended at the end of the Organization name in the TSV file, the user is considered to be a leader of that Organization.

If an email address of a user or an Organization name which does not exist on Questetra BPM Suite is written in the TSV file, an ApiException exception will be thrown from the findUser method or findGroup method.

Finally, pass the created organization set to the sync (String email, Set <String> currentGroupSet, …) method to manipulate the registered information via the Web API.

sync (String email, Set <String> currentGroupSet, …) method

With this method, the data to be changed in the organizational affiliation information are specified, and the information actually registered on Questetra BPM Suite through Web API is changed.

Finding “Organizations to add/delete affiliation”

    ...

    /**
     * Comparing registered information of User's affiliation to Organizations on Questetra BPM Suite and on TSV,
     *  manipulate the registered information via Web API.
     * @param email: Email address of the User
     * @param currentGroupSet: Set of Organizations which the User currently belongs to
     *                         of the information registered on Questetra BPM Suite
     * @param nextGroupSet: Set of Organizations of User's affiliation instructed in TSV
     * @param currentLeaderGroupSet Set of Organizations to which the User is affiliated as a 
     *                              leader, in the information currently registered on Questetra BPM Suite.
     * @param nextLeaderGroupSet: Set of Organizations of User's affiliation as a leader instructed in TSV
     * @throws ApiException
     */
    private static void sync(String email,
                             Set<String> currentGroupSet,
                             Set<String> nextGroupSet,
                             Set<String> currentLeaderGroupSet,
                             Set<String> nextLeaderGroupSet) throws ApiException {
        MembershipDataManager manager = new MembershipDataManager();

        // Create Sets which are "Organization names that exist only on TSV (to be added)" and 
        //  "Organization names that exist only on Questetra BPM Suite (to be deleted)

        // Set of Organization names to be deleted
        Set<String> willDelete = subtract(currentGroupSet, nextGroupSet);
        // Set of Organization names to be added
        Set<String> willAdd = subtract(nextGroupSet, currentGroupSet);

        for (String group : willDelete) { // Delete from affiliation
            manager.delete(email, group);
        }

        for (String group : willAdd) { // Add to affiliation
            if (nextLeaderGroupSet.contains(group))
                 manager.add(email, group, true);
            else manager.add(email, group, false);
        }
  
    ...

As with the way we found the difference in User information in “User Information Chapter”, we will also use Set operations to find out this time. First, we will make it check whether there is a change in affiliation information, and determine the Organizations to add and to delete from the User’s affiliation.

In this example, I implement and use the subtract method that will return the difference between sets when passing two sets as arguments. Among the information of affiliation to Organization currently registered on Questetra BPM Suite, a set of organizations that do not overlap with the contents of the TSV file is stored in willDelete of the Set <String> type and is targeted for deletion. Similarly, create willAdd, a set of organizations that are only included in the TSV file and must be added to affiliation to organizations.

Then, update the information on Questetra BPM Suite for each organization stored in willDelete, willAdd using the delete, add method of MembershipDataManager class. With this, the information of User’s affiliation to Organizations on Questetra BPM Suite and onTSV will be matched.

Finding out “Organizations in which status of the User has changed”

    ...

        // Finding Organizations to which the User is affiliated as a leader on Questetra but not on TSV,
        // remain Organizations to which continue to affiliate.
        // "Was a leader before, but is not now" = "Changed from leader to staff"
        //                                          or "Got out of members of the Organization (willDelete)"
        Set<String> wasLeader = subtract(currentLeaderGroupSet, nextLeaderGroupSet);
        Set<String> toStaff = subtract(wasLeader, willDelete);

        // Finding Organizations to which the User is affiliated as a leader on TSV but not on Questetra,
        // remain Organizations to which continue to affiliate.
        // "Become a new leader" = "Changed from staff to leader"
        //                          or "Newly added to the organization as a leader (willAdd)"
        Set<String> willLeader = subtract(nextLeaderGroupSet, currentLeaderGroupSet);
        Set<String> toLeader = subtract(willLeader, willAdd);

        for (String group : toStaff) { // Changed to a staff
            manager.update(email, group, false);
        }

        for (String group : toLeader) { // Changed to a leader
            manager.update(email, group, true);
        }
    }

    ...

Next, check if there is a case where “a User continues to belong to the same Organization but his/her status is changed”. Organizations to which the User’s affiliation to be changed in the synchronization has been retrieved as willDelete and willAdd in the previous section. Find out whether there is an Organization which is not included in those sets and the user’s status is changed in it.

Again, the organization that has changed in the User’s status information can be retrieved by taking the difference between two sets, which are currentLeaderGroupSet, the information on Questetra BPM Suite, and the nextLeaderGroupSet, the information on the TSV file. However, when “an Organization name to which the User is affiliated as a leader disappears”, for example, there are two reasons to be considered such as, “the User’s status has been changed from a leader to a staff” and “no longer being affiliated to the Organization.” For such a case, it is possible to narrow down to only the former case by finding a difference between willDelete which is “a set of Organizations names to which the User is no longer affiliated”. This can be applied when finding a set of Organizations in which the User’s status is changed from a staff to a leader.

After finding sets of Organizations, which are to change to staff toStaff and to change to leader toLeader, update the registered information on Qusestetra BPM Suite using the update method of MembershipDataManager class. With this, the status information of the User in affiliated Organizations will be synchronized.

subtract method

    /**
     * Returns the difference between two sets (set1 - set2)
     * @param: set1 Minuend set
     * @param: set2 Subtrahend set
     * @param: <T>
     * @return: Difference set
     */
    private static <T> Set<T> subtract(Set<T> set1, Set<T> set2) {
        Set<T> subtraction = new HashSet<>(set1);
        subtraction.removeAll(set2);
        return subtraction;
    }

This is a method used for set operations. It returns the difference set between given two sets.


Together with “User Information Chapter”, now you are able to synchronize basic User information on Questetra BPM Suite with an external file. In the User detail page, it corresponds to the part enclosed in the screenshot below.

This is the end of the series of articles regarding the manipulation of User information using Web API. Questetra BPM Suite provides various API endpoints other than the one introduced here. The documentation for com.questetra.bpms.client.swagger is available at the following link, so please refer to it.


Appendix: The entire body of codes that used

SyncMembershipWithTSV.java

import com.questetra.bpms.client.swagger.ApiException;
import com.questetra.bpms.client.swagger.model.Membership;

import java.io.IOException;
import java.util.*;

import static java.util.stream.Collectors.toSet;

class Reading TSV data {
    public static void main(String[] args) throws IOException, ApiException {
        // Read TSV data
        List<String[]> data = TSVReader.readLines("PATH OF TSV FILE");
        // Processing one line by one.
        for (String[] user : data) {
            sync(user);
        }
    }


    /**
     * Synchronizing the information of affiliation to Organization of one User
     * @param user: One line of data read from TSV
     * @throws ApiException
     */
    private static void sync(String[] user) throws ApiException {
        MembershipDataManager manager = new MembershipDataManager();

        // Retrieve the organization to which the user currently belongs on Questetra BPM Suite
        Set<Membership> currentMembershipSet = new HashSet<>(
                            manager.findMemberships(user[0]).getMemberships());
        // Create a Set of "organizations to which the user belongs as a leader"
        Set<Membership> currentLeaderSet =
                            currentMembershipSet.stream()
                                      .filter(m -> Objects.equals(
                                              m.getRole(),Membership.RoleEnum.LEADER))
                                      .collect(toSet());

        Set<String> currentGroupSet = currentMembershipSet.stream()
                                                      .map(Membership::getQgroupName)
                                                      .collect(toSet());
        Set<String> currentLeaderGroupSet = currentLeaderSet.stream()
                                                      .map(Membership::getQgroupName)
                                                      .collect(toSet());

        // Retrieve information of Organizations to which a User belongs from TSV file
        Set<String> nextMembershipSet = new HashSet<>(Arrays.asList(user)
                                                            .subList(1, user.length));
        // Create a Set of "Organization to which the User belongs as a leader"
        Set<String> nextLeaderGroupSet =
                nextMembershipSet.stream()
                                 .filter(m -> m.endsWith("!"))
                                 .map(m -> m.substring(0, m.length()-1))
                                 .collect(toSet());
        Set<String> nextGroupSet = nextMembershipSet.stream()
                                    .map(m -> {
                                        if (m.endsWith("!"))
                                            return m.substring(0, m.length()-1);
                                        else return m;}).collect(toSet());

        sync(user[0], currentGroupSet, nextGroupSet,
                      currentLeaderGroupSet, nextLeaderGroupSet);
    }

    /**
     * Comparing registered information of User's affiliation to Organizations on Questetra BPM Suite and on TSV,
     * manipulate the registered information via Web API.
     * @param email: Email address of the User
     * @param currentGroupSet: Set of Organizations which the User currently belongs to
     *                           of the information registered on Questetra BPM Suite
     * @param nextGroupSet: Set of Organizations of User's affiliation instructed in TSV
     * @param currentLeaderGroupSet: Set of Organizations to which the User is affiliated as a 
     *                              leader, in the information currently registered on Questetra BPM Suite.
     * @param nextLeaderGroupSet: Set of Organizations of User's affiliation as a leader instructed in TSV
     * @throws ApiException
     */
    private static void sync(String email,
                             Set<String> currentGroupSet,
                             Set<String> nextGroupSet,
                             Set<String> currentLeaderGroupSet,
                             Set<String> nextLeaderGroupSet) throws ApiException {
        MembershipDataManager manager = new MembershipDataManager();

        // Create Sets which are "Organization names that exist only on TSV (to be added)" and 
        // "Organization names that exist only on Questetra BPM Suite (to be deleted)

        // Set of Organization names to be deleted
        Set<String> willDelete = subtract(currentGroupSet, nextGroupSet);
        // Set of Organization names to be added
        Set<String> willAdd = subtract(nextGroupSet, currentGroupSet);

        for (String group : willDelete) { // Delete from affiliation
            manager.delete(email, group);
        }

        for (String group : willAdd) { // Add to affiliation
            if (nextLeaderGroupSet.contains(group))
                 manager.add(email, group, true);
            else manager.add(email, group, false);
        }


        // Finding Organizations to which the User is affiliated as a leader on Questetra but not on TSV,
        // remain Organizations to which continue to affiliate.
        // "Was a leader before, but is not now" = "Changed from leader to staff"
        //                                          or "Got out of members of the Organization (willDelete)"
        Set<String> wasLeader = subtract(currentLeaderGroupSet, nextLeaderGroupSet);
        Set<String> toStaff = subtract(wasLeader, willDelete);

        // Finding Organizations to which the User is affiliated as a leader on TSV but not on Questetra,
        // remain Organizations to which continue to affiliate.
        // "Become a new leader" = "Changed from staff to leader"
        //                          or "Newly added to the organization as a leader (willAdd)"
        Set<String> willLeader = subtract(nextLeaderGroupSet, currentLeaderGroupSet);
        Set<String> toLeader = subtract(willLeader, willAdd);

        for (String group : toStaff) { // Change to a staff
            manager.update(email, group, false);
        }

        for (String group : toLeader) { // Change to a leader
            manager.update(email, group, true);
        }
    }


    /**
     * Returns the difference between two sets (set1 - set2)
     * @param set1: Minuend set
     * @param set2: Subtrahend set
     * @param <T>
     * @return: Difference set
     */
    private static <T> Set<T> subtract(Set<T> set1, Set<T> set2) {
        Set<T> subtraction = new HashSet<>(set1);
        subtraction.removeAll(set2);
        return subtraction;
    }
}

MembershipDataManager.java

import com.questetra.bpms.client.swagger.api.QgroupApi;
import com.questetra.bpms.client.swagger.model.*;
import com.questetra.bpms.client.swagger.ApiClient;
import com.questetra.bpms.client.swagger.ApiException;
import com.questetra.bpms.client.swagger.Configuration;
import com.questetra.bpms.client.swagger.auth.*;
import com.questetra.bpms.client.swagger.api.MembershipApi;

import java.util.HashMap;


/**
 * List and manipulate the affiliation of Users.
 */
class MembershipDataManager {
    private final MembershipApi apiInstance;
    private final HashMap<String, QuserWithPrimaryQgroup> userMap = new HashMap<>();
    private final HashMap<String, Qgroup> groupMap = new HashMap<>();

    public MembershipDataManager() {
        ApiClient defaultClient = Configuration.getDefaultApiClient();
        // Set URL
        defaultClient.setBasePath("https://example.questetra.net/");

        // Basic Authentication
        HttpBasicAuth basic = (HttpBasicAuth)defaultClient
                                                .getAuthentication("basic");
        basic.setUsername({EMAIL ADDRESS});
        basic.setPassword({PASSWORD for Basic Authentication});

        this.apiInstance = new MembershipApi();
    }

    /**
     * Listing the User's affiliation to organization.
     * @param email: Email address of the User.
     * @return MembershipList: List of affiliated Organization.
     */
    public MembershipList findMemberships(String email) throws ApiException {
        // Search User ID of the specified email address
        Long userId = this.findUser(email).getId();
        MembershipList result = this.apiInstance.listByQuser(userId);
        return result;
    }

    /**
     * Search for Organizations (Qgroup) by Organization name
     * Throw ApiException if the Organization specified by the name doesn't exist on Questetra BPM Suite
     * @Param name: Organization name.
     * @return Qgroup: Organization object that has found by search.
     */
    private Qgroup findGroup(String name) throws ApiException {
        // Organizations that have already been found are added to HashMap
        if (this.groupMap.containsKey(name)) {
            return this.groupMap.get(name);
        }
        QgroupApi groupApiInstance = new QgroupApi();
        Qgroup result = groupApiInstance.find(null, name).getQgroup();
        // HashMapに追加
        this.groupMap.put(name, result);
        return result;
    }

    /**
     * Search for a User (QuserWithPrimaryQgroup) by email address.
     * Throw ApiException if the User with specified email address doesn't exist on Questetra BPM Suite
     *  exist on Questetra BPM Suite
     * @param email: Email address.
     * @return QuserWithPrimaryQgroup: Found User object.
     */
    private QuserWithPrimaryQgroup findUser(String email) throws ApiException {
        // Users that have already been found are added to HashMap
        if (this.userMap.containsKey(email)) {
            return this.userMap.get(email);
        }
        UserDataManager userManager = new UserDataManager();
        QuserWithPrimaryQgroup result = userManager.find(null, email).getQuser();
        // Add to HashMap
        this.userMap.put(email, result);
        return result;
    }

    /**
     * Adding organization to information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organization name
     * @param isLeader: true for a leader, false for a staff
     * @return MembershipWrapper
     */
    public MembershipWrapper add(String email, String group,
                                 boolean isLeader) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        MembershipWrapper result;

        if (isLeader) {
            result = this.apiInstance.add(userId, groupId, "_leader");
            System.out.printf("Add: %s / %s, %s\n", email, group, "leader");
        } else {
            result = this.apiInstance.add(userId, groupId, null);
            System.out.printf("Add: %s / %s, %s\n", email, group, "staff");
        }

        return result;
    }

    /**
     * Deleting organizations from information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organization name.
     */
    public void delete(String email, String group) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        this.apiInstance.delete(userId, groupId);
        System.out.printf("Deleted: %s / %s\n", email, group);
    }

    /**
     * Updating information of User's affiliation to Organizations
     * @param email: Email address of User.
     * @param group: Organizational name
     * @param isLeader: true for a leader, false for a staff
     * @return MembershipWrapper
     */
    public MembershipWrapper update(String email, String group,
                                    boolean isLeader) throws ApiException {
        // Search User ID by the specified email address
        Long userId = this.findUser(email).getId();
        // Search Organization ID by the specified Organization name
        Long groupId = this.findGroup(group).getId();

        MembershipWrapper result;

        // Change to Leader
        if (isLeader) {
            result = this.apiInstance.update(userId, groupId, "_leader");
            System.out.printf("Changed the role: %s / %s, → %s\n",
                              email, group, "leader");
        }
        // Change to Staff
        else {
            result = this.apiInstance.update(userId, groupId, null);
            System.out.printf("Changed the role: %s / %s, → %s\n",
                              email, group, "staff");
        }

        return result;
    }

}