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

In the previous article, in the purpose of trying to experientially deal with “com.questetra.bpms.client.swagger“, a Questetra BPM Suite library, we implemented a “search” on Users registered to Questetra BPM Suite solely. In this article, we are going to implement methods for manipulating registered User information and create a program which synchronizes User account information in a local TSV file with those on Questetra BPM Suite.

To make User information “that has been written in the TSV file” and “that has been registered to Questetra BPM Suite” as the same, the following steps are required.

  1. Compare TSV file contents with user information on Questetra BPM Suite
  2. From Questetra BPM Suite, remove User information that exists only on Questetra BPM Suite
  3. To Questetra BPM Suite, add User information that exists only on TSV file

In this chapter, we will add these three methods to UserDataManager class. And we will create SyncUserWithTSV class and implement the final synchronizing program using UserDataManager class.

  • Method for retrieving User information registered in Questetra BPM Suite
  • Method for adding Users to Questetra BPM Suite
  • Method for removing Users from Questetra BPM Suite

In this chapter, we will add these three methods to UserDataManager class. And we will create SyncUserWithTSV class and implement the final synchronizing program using UserDataManager class.

Editing POM.xml

We will use the common-lang library for random generation of the initial password, which will be described later in detail. Since in the Maven project, the library to be used needs to be described in POM.xml, so add the information of common-lang before changing the UserDataManager class.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>questetra</groupId>
    <artifactId>swagger-java</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.questetra</groupId>
            <artifactId>bpms.client.swagger</artifactId>
            <version>11.8.0</version>
            <scope>compile</scope>
        </dependency>

        <strong><dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency></strong>
    </dependencies>

</project>

Adding method to UserDataManager class

By editing POM.xml, you can now import the required libraries into your Java program. Now let’s implement the three methods mentioned earlier.

Method for retrieving User information registered in Questetra BPM Suite

Implement the list method that retrieves the list of user information registered in Questetra BPM Suite like the following.

/**
 * List the users registered on Questetra BPM Suite
 * @return QuserList
 */
public QuserList list() throws ApiException {
    // List the registered Users
    QuserList result = this.apiInstance.list(null, Integer.MAX_VALUE, 0);
    return result;
}

We use the list method of QuserApi class to enumerate registered users. The QuserApi.list method returns a list of user information that satisfies the conditions specified in the argument as QuserList type. There are three arguments: “query“, which passes the narrowing condition, “limit“, which is the upper limit on the number of search results to return, and “start“, which is an offset indicating from which search result to return. Since we want to retrieve all user information, we set it as null, maximum value of Integer type, and 0 respectively.

Method for adding Users to Questetra BPM Suite

Implement the add method to register Users in Questetra BPM Suite as follows.

/**
 * Newly register users in Questetra BPM Suite
 * @param name Name.
 * @param email Email address.
 * @param password Password. 8 to 100 characters, must contain each of lower/upper case alphabets, numbers and symbols.
 * @return QuserWrapper Added user.
 */
public QuserWrapper add(String name, String email, String password) throws ApiException {
    // Adding Users.
    QuserWrapper result = this.apiInstance.add(name, email, password);
    return result;
}

The add method of QuserApi class is used to add a user. As arguments, name for username, email for email address, and password for password are needed. The username and e-mail address may be obtained from the TSV file, but for the password, it would better be prepared by the program. An 8-digit random password is automatically generated if createPassword method as the following is added to the UserDataManager class.

Although a temporary password is issued, it is premised not to be used even for the User’s first login. Once users have been registered with Questetra BPM Suite, it is desirable for each User to set their own password using the password reissue function. System administrators should avoid knowing the User’s password. For that sake, this program does not output the issued temporary password anywhere.

Method to randomly generate a temporary password

// Import necessary libraries
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

import org.apache.commons.lang3.RandomStringUtils;

...

/**
 * Generate an 8-character temporary password.
 * @return String The generated temporary password.
 */
public String createPassword() {
    int length = 8; // Number of characters of a password
    List<String> chars = new ArrayList<>();

    chars.add(RandomStringUtils.randomAlphabetic(1).toLowerCase()); // Lower case alphabet
    chars.add(RandomStringUtils.randomAlphabetic(1).toUpperCase()); // Upper case alphabet
    chars.add(RandomStringUtils.randomNumeric(1)); // Number
    chars.add(RandomStringUtils.random(1, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")); // Symbol

    // Put random ASCII characters in the remaining 4 characters
    for (int i = 4; i < length; i++) {
        chars.add(RandomStringUtils.randomAscii(1));
    }

    // Shuffle the order of the characters
    Collections.shuffle(chars);

    return String.join("", chars);
}

The common-lang library added to POM.xml is used here. A random string is easily generated by using org.apache.commons.lang3.RandomStringUtils. As the default settings of Questetra BPM Suite have the following password policy, so I implemented so that generate according to it.

  • Password must be 8 to 100 characters long
  • Password must contain at least one upper case letter
  • Password must contain at least one lower case letter
  • Password must contain at least one number
  • Password must contain at least one symbol

Method for removing User from Questetra BPM Suite

At last, implement the method to remove the registered user from Questetra BPM Suite as follows.

/**
 * Remove User from Questetra BPM Suite.
 * @param id The ID of the user to be removed.
 */
public void delete(Long id) throws ApiException {
    // Remove User
    this.apiInstance.delete(id, {ENTER delegateQuserId}, {ENTER delegateQgroupId});
}

The delete method of QuserApi class is used to remove the user. It requires the following arguments.

  • id: the User to be removed
  • delegateQuserId: the User to whom the removing User’s Tasks will be forcibly assigned
  • delegateQgroupId: the Organization where the forcibly assigned User belongs

What is delegateQuserId, delegateQgroupId?

Suppose if a User to be removed holds My Tasks, then who will handle these? For such cases, there are delegateQuserId and delegateQgroupId. That is, Questetra BPM Suite has a function referred to as Delegate, which My task of one User to be allocated to another User forcibly. And it is possible to specify the forced allocation destination of My Tasks that the user to be removed holds in the QuserApi.delete method. So that the person in charge of Operating the Task changes to the specified User by passing the delegateQuserId (User ID of forced allocation destination) and delegateQgroupId (Organization ID of the forced allocation destination user) to the method. Specify the forced allocation destination considering the circumstances of the company, etc.

With the deletion of the User “Mariana”, the Task that “Mariana” held in My Tasks is forcibly allocated to the specified User

You can confirm the User ID at each user information detail page in “User List” < “System Settings”, and the Organization ID which is indicated on the detail page of Organization in “Organization List” < “System Setting”, or jump from the user information detail page. As it is indicated like “u1” and “g1” respectively, please extract only the part of the number.

These IDs are Long type

Implementing SyncUserWithTSV class

Up to this point, you have successfully implemented the UserDataManager class that manipulates User information on Questetra BPM Suite. From now on, we will implement the following functions to SyncUserWithTSV class which we are going to create newly.

  1. Read local TSV file containing user information
  2. Load user information registered on Questetra BPM Suite
  3. Compare both and determine which Users to add/remove
  4. Manipulate user information using the UserDataManager class

Read local TSV file containing User information

For example, suppose you have a TSV file that contains the information of the Users you want to register in Questetra BPM Suite as follows.

Tutorial: tutorial@questetra.com
Galapagos questetra+Galapagos@gmail.com
Oahu questetra+Oahu@gmail.com
Sumatera questetra+Sumatera@gmail.com
Canarias questetra+Canarias@gmail.com

Import this with SyncUserWithTSV class, and create a set of email addresses of Users who should be registered on Questetra BPM Suite.

# {PROJECT DIRECTORY}/src/main/java/SyncUserWithTSV.java
import com.questetra.bpms.client.swagger.ApiException;
import com.questetra.bpms.client.swagger.model.*;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

class SyncUserWithTSV {
    public static void main(String[] args) throws IOException, ApiException {
        // Read TSV data and create a set of email addresses  
        List<String[]> data = TSVReader.readLines("test2.tsv");
        Set<String> tsvSet = data.stream().map(d -> d[1]).collect(toSet());

...

}

There, I use my own TSVReader class for reading TSV files.

TSVReader class

Although there are several Java libraries for reading TSV files, in this article, we will use a class for reading which I created by myself. It is an implementation that reads all the lines at one time by putting the ones stored in the String [] type into the List <String []> type.

# {PROJECT DIRECTORY}/src/main/java/TSVReader.java
import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;

class TSVReader {
    public static List<String[]> readLines(String filePath) throws IOException {
        List<String[]> data = new ArrayList<>();

        try (InputStream is = ClassLoader.getSystemResourceAsStream((filePath))) {
            BufferedReader br = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)));

            String line = br.readLine();
            while (line != null) {
                String[] d = line.split("\t", 0);
                data.add(d);
                line = br.readLine();
            }
        }

        return data;
    }
}

Load User information registered on Questetra BPM Suite

Next, the User information registered from Questetra BPM Suite is read, and a set of email addresses is created in the same way as the TSV file. For reading, the list method which has been implemented in the UserDataManager class is used.

# {PROJECT DIRECTORY}/src/main/java/SyncUserWithTSV.java
...

        // Read User list via Questetra BPM Suite QuserApi and create email set of registered Users
        UserDataManager manager = new UserDataManager();
        QuserList list = manager.list();
        Set<String> qbpmsSet = list.getQusers().stream().map(Quser::getEmail).collect(toSet());

...

Compare both and determine which Users to add/remove

Comparing “the set of User’s email addresses written in TSV file” and “the set of User’s email addresses registered on Questetra BPM Suite”, determines the User to be removed from/to be added to Questetra BPM Suite.

# {PROJECT DIRECTORY}/src/main/java/SyncUserWithTSV.java
...

       // Create sets of "email addresses only being in TSV" and "email addresses only being on Qusetetra BPM Suite".
        Set<String> intersection = new HashSet(tsvSet);
        intersection.retainAll(qbpmsSet);
        tsvSet.removeAll(intersection); // Email addresses only being in TSV
        qbpmsSet.removeAll(intersection); // Email addresses only being on Qusetetra BPM Suite

...

There, an intersection of the two sets is taken. The elements of the intersection are “email addresses being both in TSV file and on Questetra BPM Suite”. Then respectively subtracting these from TSV file set and Questetra BPM Suite set, email addresses of “Users to be removed and to be added” are identified.

Manipulate user information using the UserDataManager class

Now, you know which Users to add and which to remove, so manipulate the User information on Questetra BPM Suite to be the same as the TSV file.

# {PROJECT DIRECTORY}/src/main/java/SyncUserWithTSV.java
...
 
       // Remove "email addresses only being in Questetra BPM Suite" from Questetra BPM Suite
        for (String email : qbpmsSet) {
            Quser deleteUser = list.getQusers().stream().filter(user -> Objects.equals(user.getEmail(), email)).collect(toList()).get(0);
            manager.delete(deleteUser.getId());
            System.out.printf("Delete User / %s%n", deleteUser.getEmail());
        }

        // Add "email addresses only being in TSV file" to Questetra BPM Suite
        for (String email : tsvSet) {
            String[] addUser = data.stream().filter(user -> Objects.equals(user[1], email)).collect(toList()).get(0);
            String password = manager.createPassword();
            manager.add(addUser[0], addUser[1], password);
            System.out.printf("Add User / %s%n", addUser[0]);
        }
    }
}

Since the ID of the user to be removed is required to use the UserDataManager.delete method, the corresponding user ID is checked using the registered user list retrieved from Questetra BPM Suite. Also, in order to use the UserDataManager.add method, a password is required in addition to the username and email address, so a temporary password is created using the UserDataManager.createPassword method. However, as mentioned earlier, temporary passwords are not output because they won’t be used.

Now, when this program is executed, the local TSV file and the User information on Questetra BPM Suite are matched. At this time, only the QuserApi class has been used among the API libraries, so the User information that can be registered is only the minimum required for login to Questetra BPM Suite; i.e. user name, email address, password. However, of course, that is not all of the User information that can be manipulated with the API library. A lot of useful classes are implemented in Questetra BPM Suite’s API library, such as MembershipApi class, which provides methods to operate Organizational assignment, and RoleMembershipApi class, which provides methods to operate Roles to be granted to users. In the next post, I will describe how to use MembershipApi class, which is capable of listing and manipulating User affiliations to Organizations.

Next: Synchronizing User Information with Local Data (Organizational Belonging Chapter)

Appendix: The entire body of codes that used

SyncUserWithTSV.java

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

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

class SyncUserWithTSV {
    public static void main(String[] args) throws IOException, ApiException {
        // Read TSV data and create a set of email addresses
        List<String[]> data = TSVReader.readLines({FILE PATH OF TSV});
        Set<String> tsvSet = data.stream().map(d -> d[1]).collect(toSet());

        // Read User list via Questetra BPM Suite QuserApi and create email set of registered Users
        UserDataManager manager = new UserDataManager();
        QuserList list = manager.list();
        Set<String> qbpmsSet = list.getQusers().stream().map(Quser::getEmail).collect(toSet());

        // Create sets of "email addresses only being in TSV" and "email addresses only being on Qusetetra BPM Suite".
        Set<String> intersection = new HashSet<>(tsvSet);
        intersection.retainAll(qbpmsSet);
        tsvSet.removeAll(intersection); // Email addresses only being in TSV
        qbpmsSet.removeAll(intersection); // Email addresses only being on Qusetetra BPM Suite

        // Remove "email addresses only being in Questetra BPM Suite" from Questetra BPM Suite
        for (String email : qbpmsSet) {
            Quser deleteUser = list.getQusers().stream().filter(user -> Objects.equals(user.getEmail(), email)).collect(toList()).get(0);
            manager.delete(deleteUser.getId());
            System.out.printf("Delete User / %s%n", deleteUser.getEmail());
        }

        // Add "email addresses only being in TSV file" to Questetra BPM Suite
        for (String email : tsvSet) {
            String[] addUser = data.stream().filter(user -> Objects.equals(user[1], email)).collect(toList()).get(0);
            String password = manager.createPassword();
            manager.add(addUser[0], addUser[1], password);
            System.out.printf("Add User / %s%n", addUser[0]);

        }
    }
}

TSVReader.java

import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Objects;

class TSVReader {
    public static List<String[]> readLines(String filePath) throws IOException {
        List<String[]> data = new ArrayList<>();

        try (InputStream is = ClassLoader.getSystemResourceAsStream((filePath))) {
            BufferedReader br = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)));

            String line = br.readLine();
            while (line != null) {
                String[] d = line.split("\t", 0);
                data.add(d);
                line = br.readLine();
            }
        }

        return data;
    }
}

UserDataManager.java

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.QuserApi;

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

import org.apache.commons.lang3.RandomStringUtils;

/**
 * Exchange user information with Questetra BPM Suite.
 */
class UserDataManager {
    private final QuserApi apiInstance;

    public UserDataManager() {
        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 Autthentication}");

        this.apiInstance = new QuserApi();
    }

    /**
     * List the Users registered on Questetra BPM Suite
     * @return QuserList
     */
    public QuserList list() throws ApiException {
        // List Users
        QuserList result = this.apiInstance.list(null, Integer.MAX_VALUE, 0);
        return result;
    }

    /**
     * Search specified users from users registered in Questetra BPM Suite. Throws an exception if not found.
     * @param id: User ID. Pass {@code null} if unknown. If passes other than {@code null}, it takes precedence over {@code email}.
     * @param email: Registered email address. Passes {@code null} if unknown.
     * @return QuserWithPrimaryQgroupWrapper: Retrieved user.
     */
    public QuserWithPrimaryQgroupWrapper find(Long id, String email) throws ApiException {
        // Search user
        QuserWithPrimaryQgroupWrapper result = this.apiInstance.find(id, email);
        return result;
    }

    /**
     * Newly register users in Questetra BPM Suite
     * @param name Name.
     * @param email Email address.
     * @param password Password. 8 to 100 characters, must contain each of lower/upper case alphabets, numbers and symbols.
     * @return QuserWrapper Added user.
     */
    public QuserWrapper add(String name, String email, String password) throws ApiException {
        // Add User
        QuserWrapper result = this.apiInstance.add(name, email, password);
        return result;
    }

    /**
     * Remove User from Questetra BPM Suite.
     * @param id The ID of the user to removed.
     */
    public void delete(Long id) throws ApiException {
        // ユーザを削除
        this.apiInstance.delete(id, {ENTER delegateQuserId}, {ENTER delegateQgroupId});
    }

    /**
     * Generate an 8-character temporary password.
     * @return String The generated temporary password.
     */
    public String createPassword() {
        int length = 8; // Number of characters of a password
        List<String> chars = new ArrayList<>();

        chars.add(RandomStringUtils.randomAlphabetic(1).toLowerCase()); // Lower case alphabet
        chars.add(RandomStringUtils.randomAlphabetic(1).toUpperCase()); // Upper case alphabet
        chars.add(RandomStringUtils.randomNumeric(1)); // Number
        chars.add(RandomStringUtils.random(1, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")); // Symbol

        // Put random ASCII characters in the remaining 4 characters
        for (int i = 4; i < length; i++) {
            chars.add(RandomStringUtils.randomAscii(1));
        }

        // Shuffle the order of the characters
        Collections.shuffle(chars);

        return String.join("", chars);
    }
}

Next: Synchronizing User Information with Local Data (Organizational Affiliation Chapter)

1 thought on “Synchronizing User Information with Local Data (User Information Chapter)”

  1. Pingback: Synchronizing User Information with Local Data (Preparatory Chapter) – Questetra Support

Comments are closed.