前: ユーザ情報をローカルデータと同期させる(準備編)

  • 準備編
  • ユーザ情報編
  • 所属組織編(準備中)

前回は Questetra BPM Suite ライブラリ com.questetra.bpms.client.swagger を試しに触ってみるというテーマで、Questetra BPM Suite に登録されているユーザの検索のみを実装しました。今回は登録ユーザ情報を操作するメソッドを実装し、ローカルの TSV ファイルと Questetra BPM Suite 上でユーザアカウント情報が一致するように同期させるプログラムを作成しましょう。

「TSV ファイルに記述されたユーザ情報」と「Questetra BPM Suite に登録されたユーザ情報」を揃えるためには、

  1. TSV ファイルの内容と Questetra BPM Suite 上のユーザ情報を比較する
  2. Questetra BPM Suite 上にしかないユーザ情報を Questetra BPM Suite から削除
  3. TSV ファイルにしかないユーザ情報を Questetra BPM Suite に追加

という手順を踏まなければなりません。そのため、Questetra BPM Suite とユーザ情報のやり取りをする役割の UserDataManager クラスには、次のようなメソッドが必要です。

  • Questetra BPM Suite に登録されたユーザ情報を取得するメソッド
  • Questetra BPM Suite にユーザを追加するメソッド
  • Questetra BPM Suite からユーザを削除するメソッド

この章ではこの 3 つのメソッドを UserDataManager クラスに追加します。そして新たに SyncUserWithTSV クラスを作成し、UserDataManager クラスを用いた最終的な同期プログラムを実装します。

POM.xml の編集

詳しくは後述しますが、今回は初期パスワードのランダム生成に common-lang ライブラリを使用します。Maven プロジェクトでは使用するライブラリを POM.xml に記述しておく必要があるので、UserDataManager クラスを変更する前に common-lang の情報を追加しておきましょう。

<?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>

UserDataManager クラスへのメソッド追加

POM.xml の編集により、必要なライブラリが Java プログラムにインポートできるようになりました。では先ほど挙げた 3 つのメソッドを実装していきましょう。

Questetra BPM Suite に登録されたユーザ情報を取得するメソッド

Questetra BPM Suite に登録されたユーザ情報の一覧を取得するメソッド list を次のように実装します。

/**
 * Questetra BPM Suite上に登録されているユーザを列挙する
 * @return QuserList
 */
public QuserList list() throws ApiException {
    // 登録ユーザを列挙
    QuserList result = this.apiInstance.list(null, Integer.MAX_VALUE, 0);
    return result;
}

登録ユーザの列挙には QuserApi クラスの list メソッドを用いています。QuserApi.list メソッドは、引数で指定された条件を満たすユーザ情報のリストを QuserList 型で返します。引数はそれぞれ絞り込みの条件を渡す query、返す検索結果数の上限 limit、何番目の検索結果から返すかを示すオフセット start の 3 つです。すべてのユーザ情報を取得したいので、それぞれ null, Integer 型の最大値, 0 としています。

Questetra BPM Suite にユーザを追加するメソッド

Questetra BPM Suite にユーザを登録するメソッド add を次のように実装します。

/**
 * Questetra BPM Suiteにユーザを新規登録する。
 * @param name 名前。
 * @param email メールアドレス。
 * @param password パスワード。8文字以上100文字以下、小文字/大文字アルファベット、数字、記号の4種を含むこと。
 * @return QuserWrapper 追加したユーザ。
 */
public QuserWrapper add(String name, String email, String password) throws ApiException {
    // ユーザを追加
    QuserWrapper result = this.apiInstance.add(name, email, password);
    return result;
}

ユーザの追加には QuserApi クラスの add メソッドを用いています。引数としてユーザ名 name、メールアドレス email、パスワード password が必要です。ユーザ名とメールアドレスは TSV ファイルから取得するとして、パスワードはプログラムの方で用意してほしい場合があるでしょう。次に示す createPassword メソッドを UserDataManager クラスに加えておくと、8桁のランダムパスワードを自動生成できます。

仮パスワードを発行してはいますが、このパスワードは初回ログインにも使用しない前提です。ユーザを Questetra BPM Suite に登録したら、各ユーザがパスワード再発行機能を用いて、自分でパスワードを設定することが望ましいでしょう。システム管理者がユーザのパスワードを知ることは避けるべきです。そのため、このプログラムでは発行した仮パスワードをどこにも出力していません。

仮パスワードをランダム生成するメソッド

// 必要なライブラリをインポート
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

import org.apache.commons.lang3.RandomStringUtils;

...

/**
 * 8 文字の仮パスワードを作成する。
 * @return String 作成された仮パスワード。
 */
public String createPassword() {
    int length = 8; // パスワードの文字数
    List<String> chars = new ArrayList<>();

    chars.add(RandomStringUtils.randomAlphabetic(1).toLowerCase()); // アルファベット小文字
    chars.add(RandomStringUtils.randomAlphabetic(1).toUpperCase()); // アルファベット大文字
    chars.add(RandomStringUtils.randomNumeric(1)); // 数字
    chars.add(RandomStringUtils.random(1, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")); // 記号

    // 残りの 4 文字にランダムな ASCII 文字を入れる
    for (int i = 4; i < length; i++) {
        chars.add(RandomStringUtils.randomAscii(1));
    }

    // 文字の順番をシャッフル
    Collections.shuffle(chars);

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

POM.xml に追記した common-lang ライブラリはここで使用しています。org.apache.commons.lang3.RandomStringUtils を用いると、ランダムな文字列が簡単に生成できます。Questetra BPM Suite のデフォルト設定では次のようなパスワードポリシーになっているので、それに沿うものを生成するよう実装しました。

  • 8 文字以上 100 文字以下
  • アルファベット大文字・小文字の両方を含むこと
  • 数字を含むこと
  • 記号を含むこと

Questetra BPM Suite からユーザを削除するメソッド

最後に、Questetra BPM Suite から登録ユーザを削除するメソッドを次のように実装します。

/**
 * Questetra BPM Suiteからユーザを削除する。
 * @param id 削除するユーザのID。
 */
public void delete(Long id) throws ApiException {
    // ユーザを削除
    this.apiInstance.delete(id, {delegateQuserIdを入力}, {delegateQgroupIdを入力});
}

ユーザの削除には QuserApi クラスの delete メソッドを用いています。引数として、削除するユーザを示す id、削除するユーザのタスクの強制割当先のユーザと所属組織を示す delegateQuserId, delegateQgroupId が必要です。

delegateQuserId, delegateQgroupId とは?

もし削除されるユーザがマイタスクを持っている場合、そのタスクは誰が処理するのでしょう? そのようなときのために delegateQuserId, delegateQgroupId があります。Questetra BPM Suite には、あるユーザのマイタスクを強制的に他のユーザに割り当てなおす、強制割当という機能があります。QuserApi.delete メソッドでは、削除されるユーザのマイタスクの強制割当先を指定することができます。delegateQuserId(強制割当先のユーザ ID), delegateQgroupId(強制割当先ユーザの所属組織 ID)をメソッドに渡すことで、タスクの処理担当者が指定したユーザに変更されます。強制割当先は社内の事情などに応じて指定してください。

ユーザ「マリアナ」の削除に伴い、「マリアナ」がマイタスクに持っていたタスクは指定のユーザに強制割当される

ユーザ ID は「システム設定 > ユーザ一覧」のそれぞれのユーザ情報の詳細ページから、組織 ID は「システム設定 > 組織一覧」やユーザ情報の詳細ページから移動できる組織情報の詳細ページから確認できます。それぞれ “u1” や “g1” のようになっているので、数字の部分だけを抜き出してください。

各種 ID は Long 型です。

SyncUserWithTSV クラスの実装

ここまでで、Questetra BPM Suite 上のユーザ情報の操作を行う UserDataManager クラスが一通り実装できました。ここからは

  1. ユーザ情報を書き込んだローカルの TSV ファイルを読み込む
  2. Questetra BPM Suite 上に登録されているユーザ情報を読み込む
  3. 両者を比較し、追加するユーザ/削除するユーザを決定する
  4. UserDataManager クラスを用いてユーザ情報を操作

という機能を新しく作成する SyncUserWithTSV クラスに実装していきます。

ユーザ情報を書き込んだローカルの TSV ファイルを読み込む

例えば次のように、Questetra BPM Suite に登録しておきたいユーザの情報をまとめた TSV ファイルがあるとします。

チュートリアル tutorial@questetra.com
ガラパゴス questetra+Galapagos@gmail.com
オアフ questetra+Oahu@gmail.com
スマトラ questetra+Sumatera@gmail.com
カナリア questetra+Canarias@gmail.com

これを SyncUserWithTSV クラスで読み込んで、Questetra BPM Suite 上に登録されているべきユーザのメールアドレスの集合を作成します。

# {プロジェクトディレクトリ}/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 {
        // TSVデータを読み込んでCSVに記述されたE-mail集合を作成
        List<String[]> data = TSVReader.readLines("test2.tsv");
        Set<String> tsvSet = data.stream().map(d -> d[1]).collect(toSet());

...

}

ここで TSV ファイルの読み込みのために、自作の TSVReader クラスを利用しています。

TSVReader クラス

TSV ファイルを読み込むための Java ライブラリはいくつか存在しますが、本記事では読み込み用のクラスを自作しています。1 行ずつ String[] 型に格納したものを List<String[]> 型にまとめるという形で、全行を一度に読み込む実装にしています。

# {プロジェクトディレクトリ}/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;
    }
}

Questetra BPM Suite 上に登録されているユーザ情報を読み込む

続いて Questetra BPM Suite 上から登録されているユーザ情報を読み込み、TSV ファイルと同じようにメールアドレスの集合を作成します。読み込みには UserDataManager クラスに実装した list メソッドを利用します。

# {プロジェクトディレクトリ}/src/main/java/SyncUserWithTSV.java
...

        // Questetra BPM Suite QuserApiからユーザリストを読み込んで登録ユーザのE-mail集合を作成
        UserDataManager manager = new UserDataManager();
        QuserList list = manager.list();
        Set<String> qbpmsSet = list.getQusers().stream().map(Quser::getEmail).collect(toSet());

...

両者を比較し、追加するユーザ/削除するユーザを決定する

「TSV ファイルに書かれているユーザのメールアドレス集合」と「Questetra BPM Suite 上に登録されているユーザのメールアドレス集合」を比較して、Questetra BPM Suite に追加するユーザ/Questetra BPM Suite から削除するユーザを決定します。

# {プロジェクトディレクトリ}/src/main/java/SyncUserWithTSV.java
...

       // 「TSVにしかないE-mail」「Questetra BPM SuiteにしかないE-mail」集合を作成
        Set<String> intersection = new HashSet(tsvSet);
        intersection.retainAll(qbpmsSet);
        tsvSet.removeAll(intersection); // TSVにしかないE-mail
        qbpmsSet.removeAll(intersection); // Questetra BPM SuiteにしかないE-mail

...

ここでは、2 つの集合の積 (intersection) を取っています。積集合は「TSV ファイルと Questetra BPM Suite 上、どちらにもあるメールアドレスの集合」になります。これを TSV ファイルの集合と Questetra BPM Suite 上の集合から引くと、それぞれ「追加するユーザのメールアドレス」「削除するユーザのメールアドレス」がわかります。

UserDataManager クラスを用いてユーザ情報を操作

追加するべきユーザと削除するべきユーザがわかったので、そのように Questetra BPM Suite 上のユーザ情報を操作しましょう。

# {プロジェクトディレクトリ}/src/main/java/SyncUserWithTSV.java
...
 
       // 「Questetra BPM SuiteにしかないE-mail」を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());
        }

        // 「TSVにしかないE-mail」をQBPMに追加
        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]);
        }
    }
}

UserDataManager.delete メソッドを使うには削除するユーザの ID が必要なので、Questetra BPM Suite から取得した登録ユーザリストを用いて該当のユーザ ID を調べています。また、UserDataManager.add メソッドを使うにはユーザ名・メールアドレスに加えてパスワードが必要なので、UserDataManager.createPassword メソッドを用いて仮パスワードを作成しています。ただし先述の通り、仮パスワードは使用しない前提なので出力していません。

これで、このプログラムを実行すれば、ローカルの TSV ファイルと Questetra BPM Suite 上のユーザ情報が一致するようになりました。今回は API ライブラリのうち使用したのは QuserApi クラスのみなので、登録できたユーザ情報はユーザ名・メールアドレス・パスワードという、Questetra BPM Suite へのログインに最低限必要なものだけです。しかし、もちろん API ライブラリで操作できるユーザ情報はこれだけではありません。所属組織を操作するメソッドが用意されている MembershipApi クラスや、ユーザに付与するロールを操作するメソッドが用意されている RoleMembershipApi クラスなど、Questetra BPM Suite の API ライブラリには便利なクラスがたくさん実装されています。次回はユーザと組織の所属関係を列挙・操作できる MembershipApi クラスの使い方について扱います。

次: QBPMS のユーザ情報をローカルデータと同期させる(所属組織編)

付録: 使用コード全文

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 {
        // TSVデータを読み込んでCSVに記述されたE-mail集合を作成
        List<String[]> data = TSVReader.readLines({TSV ファイルのパス});
        Set<String> tsvSet = data.stream().map(d -> d[1]).collect(toSet());

        // Questetra BPM Suite QuserApiからユーザリストを読み込んで登録ユーザのE-mail集合を作成
        UserDataManager manager = new UserDataManager();
        QuserList list = manager.list();
        Set<String> qbpmsSet = list.getQusers().stream().map(Quser::getEmail).collect(toSet());

        // 「TSVにしかないE-mail」「QBPMにしかないE-mail」集合を作成
        Set<String> intersection = new HashSet<>(tsvSet);
        intersection.retainAll(qbpmsSet);
        tsvSet.removeAll(intersection); // CSVにしかないE-mail
        qbpmsSet.removeAll(intersection); // QBPMにしかないE-mail

        // 「QBPMにしかないE-mail」を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());
        }

        // 「TSVにしかないE-mail」を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;

/**
 * Questetra BPM Suiteとの間でユーザ情報のやり取りをする。
 */
class UserDataManager {
    private final QuserApi apiInstance;

    public UserDataManager() {
        ApiClient defaultClient = Configuration.getDefaultApiClient();
        // URL を設定
        defaultClient.setBasePath("https://example.questetra.net/");

        // Basic 認証
        HttpBasicAuth basic = (HttpBasicAuth) defaultClient.getAuthentication("basic");
        basic.setUsername("{メールアドレス}");
        basic.setPassword("{basic認証用パスワード}");

        this.apiInstance = new QuserApi();
    }

    /**
     * Questetra BPM Suite上に登録されているユーザを列挙する
     * @return QuserList
     */
    public QuserList list() throws ApiException {
        // 登録ユーザを列挙
        QuserList result = this.apiInstance.list(null, Integer.MAX_VALUE, 0);
        return result;
    }

    /**
     * Questetra BPM Suiteに登録済のユーザから指定のユーザを検索する。見つからない場合は例外送出。
     * @param id ユーザID。わからない場合は{@code null}を渡す。{@code null}以外を渡した場合は{@code email}よりも優先される。
     * @param email 登録メールアドレス。わからない場合は{@code null}を渡す。
     * @return QuserWithPrimaryQgroupWrapper 検索されたユーザ。
     */
    public QuserWithPrimaryQgroupWrapper find(Long id, String email) throws ApiException {
        // ユーザを検索
        QuserWithPrimaryQgroupWrapper result = this.apiInstance.find(id, email);
        return result;
    }

    /**
     * Questetra BPM Suiteにユーザを新規登録する。
     * @param name 名前。
     * @param email メールアドレス。
     * @param password パスワード。8文字以上100文字以下、小文字/大文字アルファベット、数字、記号の4種を含むこと。
     * @return QuserWrapper 追加したユーザ。
     */
    public QuserWrapper add(String name, String email, String password) throws ApiException {
        // ユーザを追加
        QuserWrapper result = this.apiInstance.add(name, email, password);
        return result;
    }

    /**
     * Questetra BPM Suiteからユーザを削除する。
     * @param id 削除するユーザのID。
     */
    public void delete(Long id) throws ApiException {
        // ユーザを削除
        this.apiInstance.delete(id, {delegateQuserIdを入力}, {delegateQgroupIdを入力});
    }

    /**
     * 8文字の仮パスワードを作成する。
     * @return String 作成された仮パスワード。
     */
    public String createPassword() {
        int length = 8; // パスワードの文字数
        List<String> chars = new ArrayList<>();

        chars.add(RandomStringUtils.randomAlphabetic(1).toLowerCase()); // アルファベット小文字
        chars.add(RandomStringUtils.randomAlphabetic(1).toUpperCase()); // アルファベット大文字
        chars.add(RandomStringUtils.randomNumeric(1)); // 数字
        chars.add(RandomStringUtils.random(1, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")); // 記号

        // 残りの 4 文字にランダムな ASCII 文字を入れる
        for (int i = 4; i < length; i++) {
            chars.add(RandomStringUtils.randomAscii(1));
        }

        // 文字の順番をシャッフル
        Collections.shuffle(chars);

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

次: ユーザ情報をローカルデータと同期させる(所属組織編)