Wicket バッチ処理で、PackageTextTemplate を使ってメールテンプレートを作成する


バッチ処理(Dropwizard の Command) でメール送信処理を書く必要があり、
テンプレートエンジンを何かしら使おうとして、
Wicket の PackageTextTemplate を使用した結果を記載します。
正直かなり無理矢理感はありますが、TemplateEngine として使うことはできました。


前提

  • Wicket version 7.6.0 を使用して動作確認は実施しました。
    <dependency>
        <groupId>org.apache.wicket</groupId>
        <artifactId>wicket-core</artifactId>
        <version>7.6.0</version>
    </dependency>

参考記事


Wicket を TemplateEngine として使う

Wicket の Example Page に、Mail Templates の項があります。

Wicket Examples - Render mail templates

Example では Page、Panel、もしくは、PackageTextTemplate を使用して、 Template の取得、パラメータ設定を行っています。
以下、Example のざっくり抜粋になります。

Page を Template として使う

PageProvider、ComponentRenderer#renderPage() を使って、 テンプレート取得、パラメータ設定をします。

    PageProvider pageProvider = new PageProvider(TemplateBasedOnPage.class, parameters);
    CharSequence pageHtml = ComponentRenderer.renderPage(pageProvider);

Panel を Template として使う

ComponentRenderer.renderComponent() を使って、 テンプレート取得、パラメータ設定をします。

    CharSequence panelHtml = ComponentRenderer.renderComponent(new MailTemplatePanel("someId",
        new PropertyModel<String>(MailTemplate.this, "name")));

txt ファイルを Template として使う

PackageTextTemplate を使って、 テンプレート取得、パラメータ設定をします。

    PackageTextTemplate template = new PackageTextTemplate(MailTemplate.class, "mail-template.tmpl");
    CharSequence templateHtml = template.asString(variables);

バッチ処理内で、PackageTextTemplate を使用する

Batch 処理 (WARアプリケーションではない。Wicket Filter や、 Wicket Servlet が起動されない処理) では、 以下の実装で、 PackageTextTemplate を動作させることができました。

  • createMessageメソッド
    private String createMessage(ContactMailTaskManage entity) throws IOException {
        String message = null;

        MockApplication mock = new MockApplication();
        ThreadContext.setApplication(mock);
        mock.setName("BATCH");
        mock.setServletContext(new MockServletContext(mock, ""));
        mock.initApplication();

        try (TextTemplate textTemplate = new PackageTextTemplate(this.getClass(), "inqueryMailTmpl.txt")) {
            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("name", entity.getName());
            vars.put("emailAddress", entity.getEmail());
            vars.put("subject", entity.getSubject());
            vars.put("message", entity.getMessage());
            message = textTemplate.asString(vars);
        } catch (IOException e) {
            throw e;
        }
        return message;
    }
  • 説明 以下の記述で、MockApplication を生成して、
    ThreadContextに設定しています。
            MockApplication mock = new MockApplication();
            ThreadContext.setApplication(mock);
            mock.setName("BATCH");
            mock.setServletContext(new MockServletContext(mock, ""));
            mock.initApplication();
    
    上記を実行しないと、ThreadContextに、紐づくApplicationが存在しない状態になり、
    リソース取得時に以下のエラーが発生します。
    ! org.apache.wicket.WicketRuntimeException: There is no application attached to current thread main
    ! at org.apache.wicket.Application.get(Application.java:235)
    ! at org.apache.wicket.util.template.PackageTextTemplate.load(PackageTextTemplate.java:195)
    ! at org.apache.wicket.util.template.PackageTextTemplate.getString(PackageTextTemplate.java:255)
    ! at org.apache.wicket.util.template.TextTemplate.asString(TextTemplate.java:72)
    
    これではまっていたのですが、Wicket のテストクラスで、MockApplicationの設定記述があったので、
    そちら参考に実装して上手く動かすことができました。

無理矢理なので、TextTemplate を継承実装して、無理矢理感をなくす。

記事を記載していて、あまりに無理矢理なので、
別の方式にしたほうがいい気がしてきました。PackageTextTemplate の親クラスで、TextTemplate を継承して実装すると、
Application に依存する処理をスキップすることができるので、そちらを試してみました。

以下、変更した createMessageメソッド になります。

  • createMessageメソッド
    private String createMessage(ContactMailTaskManage entity) throws IOException {

        String message = null;

        try (TextTemplate textTemplate = new TextTemplate("text") {

            private InputStream is;
            private BufferedReader br;
            private InputStreamReader isr;

            @Override
            public String getString() {
                is = this.getClass().getResourceAsStream("inqueryMailTmpl.txt");
                isr = new InputStreamReader(is);
                br = new BufferedReader(isr);
                StrBuilder sb = new StrBuilder();
                br.lines().forEach(line -> {
                    sb.appendln(line);
                });
                return sb.toString();
            }

            @Override
            public TextTemplate interpolate(Map<String, ?> map) {
                throw new UnsupportedOperationException("Not support..");
            }

            @Override
            public void close() {
                IOUtils.closeQuietly(is);
                IOUtils.closeQuietly(isr);
                IOUtils.closeQuietly(br);
            }
        }) {

            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("name", entity.getName());
            vars.put("emailAddress", entity.getEmail());
            vars.put("subject", entity.getSubject());
            vars.put("message", entity.getMessage());
            message = textTemplate.asString(vars);
        }
        return message;
    }
  • 説明1 TextTemplate のgetString メソッド、interpolate メソッドを実装したインナークラスを作成しました。
    interpolate は、TextTemplate#asString() を使う限りは、呼び出されないので、例外をスローする実装にしました。

  • 説明2 TextTemplateは、Closeable をimplementsしているので、closeメソッドを実装する必要があります。

  • 説明3 getString() の呼び出し毎に、ファイルを取得しにいく必要はないので、パフォーマンスとしては改善の余地はあるように思います。

Application クラスに依存しない形でTextTemplateの実装はできました。
Panelなども、Application クラスに依存しない形で、純粋な TemplateEngine として使うこともできるかもしれません。
他にもTemplateEngine のライブラリは存在するので、無理に使う必要はないですが。。 以上です。

コメント

カテゴリー