LastaFlute移行 0.8.2 to 0.8.3

LastaFlute, 2016夏のリファクタリング

まずはこちらをご覧ください。

アップグレードしたライブラリは以下の通りです。

  • LastaFlute-0.8.3
  • LastaThymeleaf-0.2.8
  • LastaTaglib-0.7.1
  • JettyBoot-0.4.3
  • TomcatBoot-0.5.0
  • UTFlute-0.6.1A
  • DBFlute-1.1.1 (エンジンのみ)

Action のスーパークラスのメソッド

LastaAction

Actionの継承クラスである LastaAction から async() や required() が削除されました。以下、削除されたメソッドと今後の対応について。

async()
AsyncManagerをDIして利用
required()
TransactionStageをDIして利用

あまり頻繁に使うメソッドではないので、必要なコンポーネントをつどDIするようにお願いします。

TypicalAction

Actionの継承クラスである TypicalAction から verifyParameterExists() や currentDate(), isNotEmpty() などが削除されました。以下、削除されたメソッドと今後の対応について。

verifyParameterExists()
FormやBodyで@Required(ClientError.class)を利用
verifyParameterTrue()
verifyOrClientError()を利用
verifyTrueOr404NotFound()
verifyOrClientError()を利用
verifyTrueOrIllegalTransition()
今verifyOrIllegalTransition()を利用
throw404()
throw responseManager.new404()を利用
of404()
responseManager.new404()を利用
throwIllegalTransition()
verifyOrIllegalTransition()を利用
ofIllegalTransition()
verifyOrIllegalTransition()を利用
currentDate()
timeManager.currentDate()を利用
currentDateTime()
timeManager.currentDateTime()を利用
getUserMessage()
messageManager.getMessage(...)を利用
isEmpty()
LaStringUtil.isEmpty() もしくは StringUtils など利用
isNotEmpty()
LaStringUtil.isNotEmpty() もしくは StringUtils など利用
isCls()
FormやBodyでCDef型をプロパティとして利用
toCls()
FormやBodyでCDef型をプロパティとして利用

すでに多くの箇所で利用して修正が大変な場合は、[App]BaseAction にて必要なメソッドを以下のコードからコピーするようお願いします。 (若干、そのままコピーしても動かないところがあるかもしれませんので、微調整お願いします)

e.g. TypicalAction の削除されたところ @Java

    // ===================================================================================
    //                                                                     Verify Anything
    //                                                                     ===============
    // -----------------------------------------------------
    //                                      Verify Parameter
    //                                      ----------------
    protected void verifyParameterExists(Object parameter) { // application may call
        if (parameter == null || (parameter instanceof String && ((String) parameter).isEmpty())) {
            handleParameterFailure("Not found the parameter: " + parameter);
        }
    }

    protected void verifyParameterTrue(String msg, boolean expectedBool) { // application may call
        if (!expectedBool) {
            handleParameterFailure(msg);
        }
    }

    protected void handleParameterFailure(String msg) {
        // no server error because it can occur by user's trick easily e.g. changing GET parameter
        throw404(msg);
    }

    // -----------------------------------------------------
    //                                         Verify or ...
    //                                         -------------
    /**
     * Check the condition is true or it throws 404 not found forcedly. <br>
     * You can use this in your action process against invalid URL parameters.
     * @param msg The message for exception message. (NotNull)
     * @param expectedBool The expected determination for your business, true or false. (false: 404 not found)
     */
    protected void verifyTrueOr404NotFound(String msg, boolean expectedBool) { // application may call
        if (!expectedBool) {
            throw404(msg);
        }
    }

    /**
     * Check the condition is true or it throws illegal transition forcedly. <br>
     * You can use this in your action process against strange request parameters.
     * @param msg The message for exception message. (NotNull)
     * @param expectedBool The expected determination for your business, true or false. (false: illegal transition)
     */
    protected void verifyTrueOrIllegalTransition(String msg, boolean expectedBool) { // application may call
        if (!expectedBool) {
            throwIllegalTransition(msg);
        }
    }

    /**
     * Throw 404 exception, and show 404 error page.
     * <pre>
     * if (...) {
     *     <span style="color: #CC4747">throw404</span>("...");
     * }
     * </pre>
     * @param msg The debug message for developer (not user message). (NotNull)
     */
    protected void throw404(String msg) { // e.g. used by error handling of validation for GET parameter
        throw of404(msg);
    }

    /**
     * Create exception of 404, for e.g. orElseThrow() of Optional.
     * <pre>
     * }).orElseThrow(() <span style="color: #90226C; font-weight: bold"><span style="font-size: 120%">-</span>></span> {
     *     return <span style="color: #CC4747">of404</span>("Not found the product: " + productId);
     * });
     * </pre>
     * @param msg The debug message for developer (not user message). (NotNull)
     * @return The new-created exception of 404. (NotNull)
     */
    protected ForcedRequest404NotFoundException of404(String msg) {
        assertArgumentNotNull("msg for 404", msg);
        return new ForcedRequest404NotFoundException(msg);
    }

    /**
     * Throw illegal transition exception, as application exception.
     * <pre>
     * if (...) {
     *     <span style="color: #CC4747">throwIllegalTransition</span>("...");
     * }
     * </pre>
     * @param msg The debug message for developer (not user message). (NotNull)
     */
    protected void throwIllegalTransition(String msg) {
        throw ofIllegalTransition(msg);
    }

    /**
     * Create exception of illegal transition as application exception, for e.g. orElseThrow() of Optional.
     * <pre>
     * }).orElseThrow(() <span style="color: #90226C; font-weight: bold"><span style="font-size: 120%">-</span>></span> {
     *     return <span style="color: #CC4747">ofIllegalTransition</span>("Not found the product: " + productId);
     * });
     * </pre>
     * @param msg The debug message for developer (not user message). (NotNull)
     * @return The new-created exception of 404. (NotNull)
     */
    protected ForcedIllegalTransitionException ofIllegalTransition(String msg) {
        assertArgumentNotNull("msg for illegal transition", msg);
        final String transitionKey = newTypicalEmbeddedKeySupplier().getErrorsAppIllegalTransitionKey();
        return new ForcedIllegalTransitionException(msg, transitionKey);
    }

    // ===================================================================================
    //                                                                        Small Facade
    //                                                                        ============
    // -----------------------------------------------------
    //                                          Current Date
    //                                          ------------
    /**
     * Get the date that specifies current date for business. <br>
     * The word 'current' means transaction beginning in transaction if default setting of Framework. <br>
     * You can get the same date (but different instances) in the same transaction.
     * @return The local date that has current date. (NotNull)
     */
    protected LocalDate currentDate() {
        return timeManager.currentDate();
    }

    /**
     * Get the date-time that specifies current time for business. <br>
     * The word 'current' means transaction beginning in transaction if default setting of Framework. <br>
     * You can get the same date (but different instances) in the same transaction.
     * @return The local date-time that has current time. (NotNull)
     */
    protected LocalDateTime currentDateTime() {
        return timeManager.currentDateTime();
    }

    /**
     * Get the message for currently-requested user locale from message resources.
     * @param key The key of message managed by message resources. (NotNull)
     * @return The found message, specified locale resolved. (NotNull: if not found, throws exception)
     * @throws MessageKeyNotFoundException When the message is not found.
     */
    protected String getUserMessage(String key) {
        return messageManager.getMessage(requestManager.getUserLocale(), key);
    }

    /**
     * Get the message for currently-requested user locale from message resources.
     * @param key The key of message managed by message resources. (NotNull)
     * @param args The varying arguments for the message. (NotNull, EmptyAllowed)
     * @return The found message, specified locale resolved. (NotNull: if not found, throws exception)
     * @throws MessageKeyNotFoundException When the message is not found.
     */
    protected String getUserMessage(String key, Object... args) {
        return messageManager.getMessage(requestManager.getUserLocale(), key, args);
    }

    // -----------------------------------------------------
    //                                        Empty Handling
    //                                        --------------
    /**
     * @param str might be empty. (NullAllowed: if null, return true)
     * @return true if null or empty, false if blank or has characters.
     */
    protected boolean isEmpty(String str) {
        return LaStringUtil.isEmpty(str);
    }

    /**
     * @param str might not be empty. (NullAllowed: if null, return false)
     * @return true if blank or has characters, false if null or empty.
     */
    protected boolean isNotEmpty(String str) {
        return LaStringUtil.isNotEmpty(str);
    }

    // -----------------------------------------------------
    //                                        Classification
    //                                        --------------
    protected boolean isCls(Class<? extends Classification> cdefType, Object code) {
        assertArgumentNotNull("cdefType", cdefType);
        return LaDBFluteUtil.invokeClassificationCodeOf(cdefType, code) != null;
    }

    protected <CLS extends Classification> OptionalThing<CLS> toCls(Class<CLS> cdefType, Object code) {
        assertArgumentNotNull("cdefType", cdefType);
        if (code == null || (code instanceof String && isEmpty((String) code))) {
            return OptionalThing.ofNullable(null, () -> {
                throw new IllegalStateException("Not found the classification code for " + cdefType.getName() + ": " + code);
            });
        }
        try {
            @SuppressWarnings("unchecked")
            final CLS cls = (CLS) LaDBFluteUtil.toVerifiedClassification(cdefType, code);
            return OptionalThing.of(cls);
        } catch (ClassificationUnknownCodeException e) {
            final StringBuilder sb = new StringBuilder();
            sb.append("Cannot convert the code to the classification:");
            sb.append("\n[Classification Convert Failure]");
            try {
                sb.append("\n").append(LaActionRuntimeUtil.getActionRuntime());
            } catch (RuntimeException continued) { // just in case
                logger.info("Not found the action runtime when toCls() called: " + cdefType.getName() + ", " + code, continued);
            }
            sb.append("\ncode=").append(code);
            //sb.append("\n").append(e.getClass().getName()).append("\n").append(e.getMessage());
            final String msg = sb.toString();
            throw new ForcedRequest404NotFoundException(msg, e);
        }
    }

クライアントエラーの例外クラス

例外クラスの名前が変わりました。ForcedRequest404NotFoundException が Forced404NotFoundException という名前に変わっています。(その他、400 や 403 も同様です)

もともと、throw404() や of404() などのメソッド経由で利用されることが前提なのであまり利用箇所はないと想定されますが、[App]MultipartRequestHandler でコンパイルエラーになる可能性があります。また、コンストラクタ引数で UserMessages が必須になっていますので、メッセージが特にない場合は UserMessages.empty() を指定するようにお願いします。

e.g. 新しい Forced404NotFoundException を使った場合 @Java
throw new Forced404NotFoundException(msg, UserMessages.empty());

ログインのインターフェース

email, password固定のインターフェースではなく、抽象化された LoginCredetial を使って様々なログインの形式に対応できるようにしました。

そのため、login(), loginRedirect() などのメソッドの引数の形が変わっていますので、ログイン画面のActionなどでコンパイルエラーになります。 UserPasswordCredential を使ってユーザー名とパスワードを渡すようにお願いします。

checkUserLoginable()
new UserPasswordCredential(user, password) を引数に
login()
同じ
loginRedirect()
同じ
e.g. 新しい loginRedirect() を Credential で呼び出し @Java
return loginAssist.loginRedirect(createCredential(form), op -> op.rememberMe(form.rememberMe), () -> {
    return redirect(MypageAction.class);
});

private UserPasswordCredential createCredential(SigninForm form) {
    return new UserPasswordCredential(form.account, form.password);
}

また、[App]LoginAssist でのユーザー探しのメソッドの形式が変わっています。

doCheckUserLoginable()
checkCredential() に変更 (Credentialごとにチェック)
doFindLoginUser(user, pwd)
resolveCredential() に変更 (Credentialごとに解決)
doFindLoginUser(id)
ID指定の方は何も変わっていない (identityLogin()で呼ばれる方)

パスワードの暗号化は、スーパークラス組み込みではなく、[App]LoginAssist の中で encryptPassword() を呼び出す形になっていますので、忘れずにお願いします。

e.g. 新しい LoginAssist のユーザー探しのメソッドたち @Java
// ===================================================================================
//                                                                           Find User
//                                                                           =========
@Override
protected void checkCredential(CredentialChecker checker) {
    checker.check(UserPasswordCredential.class, credential -> {
        return memberBhv.selectCount(cb -> arrangeLoginByCredential(cb, credential)) > 0;
    });
}

@Override
protected void resolveCredential(CredentialResolver resolver) {
    resolver.resolve(UserPasswordCredential.class, credential -> {
        return memberBhv.selectEntity(cb -> arrangeLoginByCredential(cb, credential));
    });
}

private void arrangeLoginByCredential(MemberCB cb, UserPasswordCredential credential) {
    cb.query().arrangeLogin(credential.getUser(), encryptPassword(credential.getPassword()));
}

@Override
protected OptionalEntity<Member> doFindLoginUser(Integer userId) {
    return memberBhv.selectEntity(cb -> cb.query().arrangeLoginByIdentity(userId));
}

また、LoginAssist の getSessionUserBean() が getSavedUserBean() に変わっています。 getSessionUserBean() は非推奨になっているので、[App]BaseAction の getUserBean() で警告がでます。 getSavedUserBean() に変更をお願いします。

e.g. [App]BaseAction の getUserBean() で 新しい getSavedUserBean() を使う @Java
@Override
protected OptionalThing<HarborUserBean> getUserBean() { // application may call, overriding for co-variant
    return loginAssist.getSavedUserBean();
}

webcls を appcls に変更

Web区分値をやめ、アプリ区分値に変更しました。DB区分値でもなくWebでもない区分値を定義できるようにするためです。

appcls は、2016/8/6 のパッチの当たった DBFlute-1.1.1 にて利用できます。すでに 1.1.1 の場合は、DBFluteエンジンをダウンロードして上書きしてください(DBFluteランタイムは関係ありません)。 manage の upgrade は、同じバージョンで自分自身を上書きできないので、手動でダウンロードして差し替えてください。

DBFluteのアップグレードができたら、以下の修正をします。

  • lastafluteMap.dfprop の freeGenList の中の webcls を appcls に変更
  • src/main/resources の下の [app]-webcls.dfprop を [app]-appcls.dfprop に変更
  • WebCDef.java を削除。FreeGen を叩くと AppCDef.java が自動生成される

詳しくは、lastaflute-example-harbor のソースコードをご覧ください。

そもそも webcls なんて使ってない、src/main/resources の下に [app]-webcls.dfprop ファイルが存在しない方は、新たに appcls を導入してぜひ活用してください。独自の enum をあまり作る必要がなくなります。

Exampleデフォルトの変更

Exampleデフォルトは、LastaFlute組み込みのクラスではないため、互換性的には気にする必要はありません。 ただ、LastaFluteのポリシー追従という意味合いで影響する部分があれば、移行できると良いでしょう。

詳しくは、lastaflute-example-harbor のソースコードをご覧ください。

Action の toDate() を DisplayAssist に

[App]BaseAction にあった、toDate() や toStringDate() を、DisplayAssist クラスに移しました。 他の Manager たちと同様に、意識して DI して利用するようにしています。 (DisplayAssist が適切な名前かどうかはまだちょっと議論がありますが...)

Action のページング関連メソッドを PagingAssist に

[App]BaseAction にあった、registerPagingNavi() など、PagingAssist クラスに移しました。 他の Manager たちと同様に、意識して DI して利用するようにしています。

また、[app]_config.properties からページング関連のコンフィグが削除されています。 (ページングのページサイズなどは画面によって変わることも多く、あまりコンフィグに入れる意義が少ないだろうと判断して)

[App]BaseAction のdocumentメソッドオーバーライドやめ

ApplicationオリジナルのFacedeメソッドがなくなったので、オーバーライドをやめています。

[App]LoginAssist や [App]Config のインスタンス変数名

[App]LoginAssist や [App]Config を DI したときのインスタンス変数名 を、[app]部分は省略してシンプルにしました。 [App]LoginAssist なら loginAssist, [App]Config なら config となっています。

[App]HeaderBean を HeaderBean に

[App]HeaderBean を HeaderBean に変更して、かつ、viewパッケージの下に移動しています。

[App]BaseAction の User Info の定義順序など

[App]BaseAction の User Info の定義順序を調整、サブタグコメントで Application Info や Login Info と分けています。(ログインを使わないアプリの場合に、識別しやすいように)

e.g. 新しい harbor の [App]BaseAction の User Info 部分 @Java
// ===================================================================================
//                                                                           User Info
//                                                                           =========
// -----------------------------------------------------
//                                      Application Info
//                                      ----------------
@Override
protected String myAppType() { // for framework
    return APP_TYPE;
}

// -----------------------------------------------------
//                                            Login Info
//                                            ----------
// #app_customize return empty if login is unused
@Override
protected OptionalThing<HarborUserBean> getUserBean() { // application may call, overriding for co-variant
    return loginAssist.getSavedUserBean();
}

@Override
protected OptionalThing<String> myUserType() { // for framework
    return OptionalThing.of(USER_TYPE);
}

@Override
protected OptionalThing<LoginManager> myLoginManager() { // for framework
    return OptionalThing.of(loginAssist);
}

TomcatBoot-0.5.0

TomcatBoot-0.5.0 から、デフォルトで jasper への依存を無くしました。 なので、もし JSP を利用されている場合は、TomcatBoot の dependency に加えて、jasper の dependency を追加してください。

e.g. TomcatBoot-0.5.0 から、JSPを使うためには jasper を追加 @Java

<dependency>
	<groupId>org.dbflute.tomcat</groupId>
	<artifactId>tomcat-boot</artifactId>
	<version>${tomcat.boot.version}</version>
</dependency>
<dependency> <!-- for jsp -->
	<groupId>org.apache.tomcat</groupId>
	<artifactId>tomcat-jasper</artifactId>
	<version>8.5.4</version>
</dependency>

UTFlute-0.6.1A

UTFlute が 0.6.1A にアップグレードしたときの移行です。

[App]ActionDefTest にメソッドを二つ追加

test_injectedResourceDefinitionPolice() と test_lastaPresentsSomethingPolice() の二つ。

test_injectedResourceDefinitionPolice()
@ResourceアノテーションによるDI定義のポリシーチェック e.g. 必ずprivateフィールドに
test_lastaPresentsSomethingPolice()
Lastaが将来的に何かをチェック (気付いた時点で新しいUTFluteに入れていく)
e.g. [App]ActionDefTest に新しく追加 @Java
public void test_injectedResourceDefinitionPolice() throws Exception {
    policeStoryOfJavaClassChase(new InjectedResourceDefinitionPolice().shouldBePrivateField(field -> {
        return true; // means all fields
    }));
}

public void test_lastaPresentsSomethingPolice() throws Exception {
    policeStoryOfJavaClassChase(new LastaPresentsSomethingPolice());
}

☆テストケースのDIがルーズではなくなった

今まで、テストケースのクラスでは、@Resourceを付けなくてもDIが可能でした(S2Unitのポリシーをずっと継承していた)。 かなりルーズなDIを許していましたが、非常に紛らわしいことが多々ありましたので、普通のクラスと同様のルールでDIするようにしています。

e.g. テストケースの中でのDIでも、ちゃんと @Resource を付けるように @Java
@Resource
private MemberBhv memberBhv;

もし、修正範囲がたくさんあり過ぎて一気に直し切れない場合は、アプリのテストケースのスーパークラスにて、isUseTestCaseLooseBinding() をオーバーライドして、true を戻すようにしてください。以前と同じ挙動になります。

DBFlute-1.1.1 (patch-20160818)

DBFluteエンジンの 1.1.1 がパッチ更新されました。 LastaFluteのアップグレードと同時に、最新版のDBFluteエンジンをダウンロードして、アップグレードしてください。 既に 1.1.1 の場合は、上書きしてください。アップグレードしたら、テーブルクラスの自動生成、および、FreeGen を叩きます。

特に移行で気をつけるところはありませんが、CDef に Optional を戻すメソッドが追加されていたりと、便利になっています。