S2Buriでユーザ権限ごとにアクセス制御(2)

isUserInRole()はデータがフローを流れている間余り必要ないので使うようにしてません。
詳細はburi2.xpdlのBuriWorkFlowsUtilをみてみると・・・
id:makotan

勘違いしてburi2.diconを丁寧に調べていた私がやってきましたよ。buri2.xpdlなんですね...。orz

気を取り直して。

buri2.xpdlって設計資料なんですね。BuriEngineはどういうロジックでActivityを選択するのかっていう。
とか思ったらもしかして違う?これ自体、S2Buriの内部処理を制御する設定ファイルそのもの?
どうもそれっぽいですね。

というか面白いですね。こうやって使うことで複雑なif文をコードから撲滅するわけですか。なるほど。ちょっと見えてきた。単なる帳票系ワークフローエンジンってわけではなさそう。


で、本題。ユーザ権限のチェックです。

buri2.xpdlの「Providerが設定されてるとき」Activityで

#activityList = WorkFlowsUtil.getActivityListByParticipant(#activityList,#args.participant)

というOGNL式が設定されていましたがこれですかね?
このWorkFlowsUtilというコンポーネントは、buri2.diconに

	<component name="WorkFlowsUtil" class="org.seasar.buri.xpdl.util.impl.WorkFlowsUtilImpl">
		<aspect>BuriInterceptorChain</aspect>
		<property name="pathUtil">pathUtil</property>
		<!--<property name="activitySelectRule">wakanago.ActivitySelectRule</property>-->
		<!--<property name="activitySelectRule">BuriActivitySelectRule</property>-->
		<aspect pointcut="getActivity,getActivityTagSelectList" >UtilCache</aspect>
	</component>

	<component name="BuriActivitySelectRule"  class="org.seasar.buri.xpdl.rule.impl.BuriActivitySelectRule">
		<aspect>BuriInterceptorChain</aspect>
	</component>

と定義されています。
ActivitySelectRuleが両方コメントアウトされてる?って一瞬悩みましたが、Seasar2だから自動インジェクションされてるだけですね。しばらくJavaじゃない世界に行ってたのですっかりぼけてました。


まあとりあえず、WorkFlowsUtilImplの実装内容をみてみます。

    public List getActivityListByParticipant(List activityList,BuriParticipant participant) {
        List result = new ArrayList();
        if(activityList == null || activityList.size() == 0) {
            return result;
        }
        ActivityTagSelect tagSelect = (ActivityTagSelect)activityList.get(0);
        BuriPath fstPath = tagSelect.getBuriPath();
        return participantUtil.findParticipant(fstPath,activityList,participant);
    }

ってことで、ParticipantUtilに続きます。

同じくburi2.diconに

	<component name="ParticipantUtil" class="org.seasar.buri.xpdl.util.impl.ParticipantUtilImpl">
		<aspect>BuriInterceptorChain</aspect>
		<property name="pathUtil">pathUtil</property>
		<aspect pointcut="getParticipantList,getParticipantFromName,getParticipant,getParticipantArray" >UtilCache</aspect>
	</component>

とあるのでこれが自動インジェクションされてるわけですね。

ParticipantUtilImplが↓です。

    public List findParticipant(BuriPath buriPath,List activityList,BuriParticipant participant) {
        WorkFlowPackageTagSelect packageTagSelect = flowsUtil.getWorkFlowPackage(buriPath);
        List result = new ArrayList();
        ParticipantProvider provider = packageTagSelect.getParticipantProvider();
        Iterator ite = activityList.iterator();
        int roleTypeID = 100;
        while(ite.hasNext()) {
            ActivityTagSelect activity = (ActivityTagSelect)ite.next();
            roleTypeID = appendActivity(buriPath,result,activity,participant,provider,roleTypeID);
        }
        return result;
    }
    
    protected int appendActivity(BuriPath buriPath,List result,ActivityTagSelect activity,BuriParticipant participant,ParticipantProvider provider,int roleTypeID) {
        Participant xpdlParticipant = getParticipantFromName(buriPath,activity.getActivity().getPerformer());
        if(xpdlParticipant==null) {
            throw new BuriNoParticipantException(buriPath,participant.getUserData().toString());
        }
        if( canExecuteParticipant(xpdlParticipant,participant,provider) ) {
            int newRoleTypeID = convParticipantTypeToVal(xpdlParticipant.getParticipantType());
            if(roleTypeID > newRoleTypeID) {
                result.clear();
                roleTypeID = newRoleTypeID;
            }
            if(roleTypeID == newRoleTypeID) {
                result.add(activity);
            }
        }
        return roleTypeID;
    }
    
    protected int convParticipantTypeToVal(ParticipantType participantType) {
        Long val = (Long)convTypeToVal.get(participantType.getType());
        return val.intValue();
    }

    protected boolean canExecuteParticipant(Participant xpdlParticipant,BuriParticipant participant,ParticipantProvider provider) {
        String participantName = xpdlParticipant.getName(); 
        return provider.isUserInRole(participant.getUserData(),participantName);
    }

ちょっと長いですが、canExecuteParticipant()の中に待望のParitipantProvider#isUserInRole()が見えます。
これが毎回呼ばれない!っていうので困ってるのですが、もうちょっとでわかりそう。

canExecuteParticipant()はappendActivity()から呼ばれます。
appendActivity()内では、XPDLで対象Activityに対応付けられたParticipant情報を取得して、それがnullじゃなければcanExecuteParticipant()を呼んでいます。テスト中のXPDLではRoleを設定してあるので、canExecuteParticipant()は呼ばれるはず。つまり、省略される可能性があるとしたらもっと上流。

appendActivity()はfindParticipant()から呼ばれます。
findParticipant()では発見済みのActivity一覧の要素ごとにループしながらappendActivity()を呼んでいます。テストコードでは最低でも1件のActivityは見つかっているので、少なくとも1度は呼ばれるはず。つまり、省略される可能性があるとしたらもっと上流。

ということで、WorkFlowsUtilImpl#getActivityListByParticipant()まで戻ります。
このメソッドの中ではActivityがnullまたは0件じゃなければ、findParticipant()を呼んでいます。テストコード(ry

んじゃあ、WorkFlowsUtilImpl#getActivityListByParticipant()を呼んでいるのは?
となると、buri2.xpdlに戻るわけです。


つまり、テストコードで初回登録時以外のActivityを実行するときは、実はburi2.xpdlの「Providerが設定されてるとき」Activityではないルートを通っている、ということになりそうです。

と、そこまで書いてなんとなくわかってきました。

紛らわしいので、buri2.xpdl上のActivityをActivity@buri2、テスト用XPDLのActivityをActivity@テストと書くことにします。

で、「データIDがあるとき」Activity@buri2からの分岐で、既にActivity@テストが1つに絞られていると「一つだけ」Activity@buri2にショートカットしてしまうのです。だから、ParticipantProvider#isUserInRole()が実行されないわけです。

ふう。やっとつながった。


で、どうしようかな、と。

Activity@テストが1つに絞られていても「Activityが準備終わり」Activity@buri2に遷移するように、Transitionをとっぱらってしまえばよさそうですね。

やってみます。buri2.xpdlで、以下の修正をします。

  • 「PathにActivityがあるとき」の系列:
    • 「データIDがあるとき」→「一つだけ」へのTransitionを削除します。
    • 「データIDがあるとき」→「Activityが準備終わり」へのTransitionのConditionを削除します。
  • 「PathにActivityがないとき」の系列:
    • 「データIDがあるとき」→「一つだけ」へのTransitionを削除します。
    • 「データIDがあるとき」→「Activityが準備終わり」へのTransitionのConditionを「#activityList.size() > 0」に変更します。

で、buildしてjarを作って、入れ替えて、実行すると…、

あ、できた。

全部のActivity@テストでisUserInRole()による権限チェックが入るようになりました。

つまり、こういうことでOkですか?

あ、何もjarファイル内のwakanago.diconとburi2.xpdlを入れ替えなくても、自分のプロジェクト内でのburi2.diconで差し替えてしまえばいいのか。

追記

自分のテスト用プロジェクトに、buri2.xpdlとwakanago.diconをコピーしてきて、buri2.diconからそのwakanago.diconをincludeして、wakanago.diconからburi2.xpdlを読み込むように設定したら、あっさりとできました。
この辺りの柔軟性の高さがDIコンテナの良いところ。すばらしい。