システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

ExceptionHandlerWrapperでInjectできないけど、ExceptionHandlerFactoryにはInjectできる

BeanValidationの結果をExceptionHandlerでメッセージ出力させようと思って色々やっていた足跡みたいなものです。 とりあえず、あとから振り返りが出来るようにするためのメモ。 (独自の実装クラスとか ありますが そのあたりの説明は省略します)

事象

ryoichi0102.hatenablog.com

全く同じ事象を経験されていた方がいました。

対処

そこで、ふと思いました。
生成するタイミング(つまりFactory)だったらInjectionできるんじゃないかな?と。

結論

ExceptionHandlerFactory にInjectしたインスタンス
ExceptionHandlerに渡せば できました。

実装

injectionしたいインターフェース

public interface ViewMessage {
    public void appendMessage(Set<ConstraintViolation<Object>> validatedResults);
}

injectionしたいクラス

public class JsfViewMessage implements ViewMessage {

    @Override
    public void appendMessage(Set<ConstraintViolation<Object>> validatedResults) {

        FacesContext facesContext = FacesContext.getCurrentInstance();

        MessageInterpolatorFactory interpolatorFactory
                                   = MessageInterpolatorFactory.of("Messages", "FormMessages", "FormLabels");

        MessageInterpolator interpolator = interpolatorFactory.create();

        validatedResults.stream()
                .map(result -> {
                    return interpolator.toMessage(result);
                })
                .distinct()
                .forEach(message -> {

                    FacesMessage facemsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null);
                    facesContext.addMessage(null, facemsg);
                });

        // リダイレクトしてもFacesMessageが消えないように設定
        facesContext.getExternalContext().getFlash().setKeepMessages(true);

    }

}

Producer

@Named
@Dependent
public class ViewMessageProducer {

    @Produces
    public ViewMessage getViewMessage() {
        return new JsfViewMessage();
    }

}

ExceptionHandlerFactory

public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {

    private final ExceptionHandlerFactory parent;

    public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Inject
    ViewMessage message;

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler(), message);
        return handler;
    }
}

ExceptionHandler

メッセージ出力の実装とか ちょっとしていますが、ポイントはコンストラクタで Factoryで取得したインスタンスを使うところ。

public class CustomExceptionHandler extends ExceptionHandlerWrapper {

    private final ExceptionHandler wrapped;

    private final ViewMessage viewMessage;

    CustomExceptionHandler(ExceptionHandler exception, ViewMessage viewMessage) {
        this.wrapped = exception;
        this.viewMessage = viewMessage;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return this.wrapped;
    }

    @Override
    public void handle() {

        final Iterator<ExceptionQueuedEvent> it = getUnhandledExceptionQueuedEvents().iterator();

        while (it.hasNext()) {

            ExceptionQueuedEventContext eventContext = it.next().getContext();

            try {
                // ハンドリング対象のアプリケーション例外を取得
                Throwable th = getRootCause(eventContext.getException()).getCause();

                // 任意の例外毎に処理を行う
                this.handleBeanValidationException(th);

            } catch (IOException ex) {
                System.out.println(Arrays.toString(ex.getStackTrace()));

            } finally {
                // 未ハンドリングキューから削除する
                it.remove();
            }
        }
        getWrapped().handle();
    }

    void handleBeanValidationException(Throwable th) throws IOException {
        if (th instanceof BeanValidationException == false) {
            return;
        }

        BeanValidationException ex = (BeanValidationException) th;

        Set<ConstraintViolation<Object>> results = ex.getValidatedResults();

        viewMessage.appendMessage(results);

        FacesContext context = FacesContext.getCurrentInstance();

        String contextPath = context.getExternalContext().getRequestContextPath();
        String currentPage = context.getViewRoot().getViewId();
        context.getExternalContext().redirect(contextPath + currentPage);
    }

}

さいごに

という感じで Exceptionを扱うことは出来るには出来たのですが

警告:   #{userRegistrationAction.confirm()}: ddd.domain.validation.BeanValidationException
javax.faces.FacesException: #{userRegistrationAction.confirm()}: ddd.domain.validation.BeanValidationException
(以下、略)

標準出力に上述のログが出力される。。 (#{userRegistrationAction.confirm()} はトリガーとなったアクション)

このログは本来出力不要なものなので困った。。

Interceptorを使えば、当然 このようなログを出力することなく処理は可能。

デバッグで確認した限りでは、このログはExceptionHandlerの制御外で出力されているみたいなので、はてさてどうしたものか。。

結局、Interceptorでやるのが無難なのかなぁ。

追記