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

Javaで主にシステム開発をしながら思うところをツラツラを綴る

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でやるのが無難なのかなぁ。

追記