Tuesday, April 21, 2020

ユーザ、グループと組織の関係に役立つ便利なツール(その1)

Liferayカスタマイズのビジネスロジックに、以下のような処理がよくあると思います。


  • ユーザにアサインしたロールを取得する
  • ユーザの所属組織を取得する
  • ユーザの所属サイトを取得する
  • 組織に所属するユーザを取得する
  • サイトのサイトメンバーを取得する
  • など...


みなさまはこのような処理を行う時、userLocalService.getGroupUsers(グループのユーザを取る)、organizationLocalService.getUserOrganizations(ユーザが所属する組織を取得)などのメソッドをよく利用しますか?実は、これは間違っています。

その原因は、Liferayのサービスから取得できるものは、データベースの中に直接存在するレコードのみです。ただし、組織、サイトは階層構造のため、ユーザがサイトに所属する際、ユーザ-サイトに直接関連するデータベースレコードが存在しないこともできます(例えば、ユーザが所属するユーザグループがサイトメンバとしてサイトにアサインすることができます)。

そのため、データベース上直接関連がないエンティティを取るため、再帰的に階層構造をトラバースしないといけません。そこで、Liferayは階層構造を配慮した便利なツールを提供しています。

今回はユーザからユーザの所属サイト、ユーザグループ、組織またはユーザにアサインしたロールを取るツールを紹介したいと思います。次回は逆方向でサイト、ユーザグループ、組織から所属ユーザを取るツールです。

UserBag

以下のコードでUserBagが作成できます。

UserBag userBag = UserBagFactoryUtil.create(userId);

作成されたUserBagを利用し、以下の処理を行えます。

  • userBag.getRoles()
    • ユーザにアサインしたロールを取得する。
  • userBag.getUserUserGroupIds()
    • ユーザが所属するユーザグループのIDを取得する
  • userBag.getUserOrgs()
    • ユーザが所属する組織を取得する。
  • userBag.getUserGroups()
    • ユーザが所属するサイトを取得する。
  • userBag.getGroups()
    • ユーザが所属するグループを取得する。
※ Liferayには、ユーザグループ、サイトと組織その三つのエンティティに全部groupと関連しています。そのため、getUserGroups()メソッドはユーザグループではなく、ユーザの所属グループをリターンします。
※ 実に、ユーザグループと組織に繋がるgroupは、ユーザグループまたは組織のプロファイルページと見られます。

検証

さて、Liferayを起動してUserBagの効果を検証しましょう。ひとまず、検証用モジュールを作りましょう。

@Component(
 property = {
  "osgi.command.scope=liferay",
  "osgi.command.function=checkUserOrganizations",
  "osgi.command.function=checkUserSites",
  "osgi.command.function=checkUserRoles"
 },
 service = SiteUserOrgCmd.class
)
public class SiteUserOrgCmd {

 private static final long companyId = PortalUtil.getDefaultCompanyId();

 public void checkUserOrganizations(String screenName) throws Exception {

  User user = UserLocalServiceUtil.getUserByScreenName(companyId, screenName);
  UserBag userBag = UserBagFactoryUtil.create(user.getUserId());

  System.out.println("User's organizations in userBag: ");
  for (Organization org : userBag.getUserOrgs()) {
   System.out.println("  " + org.getName());
  }
  System.out.println("User's organizations in service: ");
  for (Organization org : OrganizationLocalServiceUtil.getUserOrganizations(user.getUserId())) {
   System.out.println("  " + org.getName());
  }
 }

 public void checkUserSites(String screenName) throws Exception {

  User user = UserLocalServiceUtil.getUserByScreenName(companyId, screenName);
  UserBag userBag = UserBagFactoryUtil.create(user.getUserId());

  System.out.println("User's sites: ");
  for (Group grp : userBag.getUserGroups()) {
   System.out.println("  " + grp.getName(Locale.getDefault()));
  }

  System.out.println("User's sites in service: ");
  for (Group grp : GroupLocalServiceUtil.getUserGroups(user.getUserId(), true)) {
   System.out.println("  " + grp.getName(Locale.getDefault()));
  }

 }

 public void checkUserRoles(String screenName) throws Exception {

  User user = UserLocalServiceUtil.getUserByScreenName(companyId, screenName);
  UserBag userBag = UserBagFactoryUtil.create(user.getUserId());

  System.out.println("User's roles: ");
  for (Role r : userBag.getRoles()) {
   System.out.println("  " + r.getName());
  }

  System.out.println("User's roles in service: ");
  for (Role r : RoleLocalServiceUtil.getUserRoles(user.getUserId())) {
   System.out.println("  " + r.getName());
  }

 }
}

※ Liferayユーザグループが階層構造になれないため、検証内容から外します。

続いて、検証用エンティティを用意します。
- ユーザ: ub_user
- ユーザグループ: ub_grp
- 組織: ub_org
  - 階層組織: ub_org/ub_org1
- サイト: ub_site
- 一般ロール: ub_role

それては、gogo shellでUserBagの効果を検証しましょう。
※ 検証中、明記以外の場合、「検証の度にユーザ、ロール、サイトと組織の関係を元に戻す」を前提として検証操作をします。

組織

以下の状況を考えてみましょう。
  •  ub_org <- ub_user
    • ub_orgは階層構造中一番トップな組織ため、UserBagOrganizationLocalServiceの戻り値は同じです。
g! checkUserOrganizations ub_user
User's organizations in userBag:
  ub_org
User's organizations in service:
  ub_org

  • ub_org1 <- ub_user
    •   ub_org1ub_orgの一個下の階層の組織のため、ユーザが直接にub_org1のユーザとして追加すると、OrganizationLocalServiceはDBに直接存在するレコードub_org1をリターンします。それに対して、UserBagは階層構造の上の組織ub_orgもリターンします。
g! checkUserOrganizations ub_user
User's organizations in userBag:
  ub_org
  ub_org1
User's organizations in service:
  ub_org1

サイト

以下の状況を考えてみましょう。
  • ub_site <- ub_user
    • ユーザが直接にサイトメンバーとしてサイトへ登録すると、UserBagGroupLocalServiceの出力が同じです。
g! checkUserSites ub_user
User's sites:
  ub_site
User's sites in service:
  ub_site

  • ub_site <- ub_grp, ub_grp <- ub_user
    • ユーザが直接にサイトへ登録ではなく、ユーザグループに通じでサイトメンバーへ登録際、UserBagGroupLocalServiceの出力が同じです。
  • 最初に「あれ?」と思う方がいらっしゃいませんか?
    • 実は、GroupLocalServiceUtil.getUserGroups(long userId, boolean inherit))のパラメーターとしてinherit=trueを指定したら、GroupLocalServiceでもUserBagのような出力ができます。
g! checkUserSites ub_user
User's sites:
  ub_site
User's sites in service:
  ub_site

  • ub_site <- ub_org, ub_org <- ub_user
    • ユーザが直接にサイトへ登録ではなく、ユーザグループに通じでサイトメンバーへ登録際、UserBagGroupLocalServiceの出力が同じです。
    • 理由はユーザグループの場合と同じです。
    • ちなみに、組織サイトも結果に入りました。
g! checkUserSites ub_user
User's sites:
  ub_org
  ub_site
User's sites in service:
  ub_org
  ub_site


  • ub_site <- ub_org, ub_org1 <- ub_user
    • ちょっと変な結果ですが、ub_userを直接に組織ub_orgの下に置かなくて、ub_org下のub_org1にアサインしたとき、ub_userub_siteのサイトメンバーとして認識されないようです。
    • これはLiferayの仕様(liferay-7.1-sp2, dxp-14-7110まで)だそうです(サイトメンバー管理画面にも同じ結果が確認できます)。
g! checkUserSites ub_user
User's sites:
  ub_org1
User's sites in service:
  ub_org1

ロール

以下の状況を考えてみましょう。
  • ub_role <- ub_user
    • ユーザが直接にロールをアサインするとき、UserBagRoleLocalServiceの出力が同じです。
g! checkUserRoles ub_user
User's roles:
  User
  ub_role
User's roles in service:
  User
  ub_role

  • ub_role <- ub_grp, ub_grp <- ub_user
    • ユーザがユーザグループを通じてユub_roleにアサインされるとき、データベースにレコードがいないため、UserBagRoleLocalServiceの出力が違います。
g! checkUserRoles ub_user
User's roles:
  User
  ub_role
User's roles in service:
  User

  • ub_role <- ub_org, ub_org1 <- ub_user
    • ユーザが組織を通じてユub_roleにアサインされるとき、データベースにレコードがいないため、UserBagRoleLocalServiceの出力が違います。
    • ub_org1は階層構造上ub_orgの下の階層のため、ユーザはub_roleを持っています。
g! checkUserRoles ub_user
User's roles:
  User
  ub_role
User's roles in service:
  User

まとめ

今回は、Liferayが提供したUserBagを利用し、ユーザから当該ユーザの所属とロールを取得する方法を紹介しました。UserBagはLiferayの階層構造を配慮して結果をリターンするため、カスタマイズの際役に立つでしょう。また、公式github上のソースコードを参考すると、UserBagをより深く理解できますので(UserBagImpl, UserBagFactoryImpl)、ぜひおすすめします。

ちなみに、UserBagの定義はLiferayのkernelモジュールであるcom.liferay.portal.security.permissionの中にあり、LiferayのpermissionCheckerはよくこのクラスを利用しています。これは本来UserBagを開発する理由でしょう。

No comments: