EclipseLinkとHibernateではTemporalType.DATEなフィールドの型が違う

かなり久々のJPAネタ。

こんなエンティティクラスがあって、

@Entity
public class Employee implements Serializable {
    @Id
    @Column(name = "emp_id")
    private Integer empId;
    
    @Temporal(TemporalType.DATE)
    @Column(name = "joined_date")
    private java.util.Date joinedDate;
    

joinedDateというフィールドはjava.util.Date型、そして@Temporal(TemporalType.DATE)が付いています。

これをEclipseLinkで実行すると、joinedDateにはjava.util.Date型のインスタンスが代入される。

一方、Hibernateで実行すると、joinedDateにはjava.sql.Date型のインスタンスが代入される。(java.sql.Datejava.util.Dateのサブクラスです)

Webアプリのビューでそのまま表示すると、Payara(JPA実装がEclipseLink内包)だと「Wed Apr 01 00:00:00 JST 2015」という形式なのに、

WildFly(JPA実装がHibernate)だと「2015-04-01」という形式になったので、アレ?と思って、

employee.getJoinedDate().getClass().getName()してクラス名を表示したらこんな感じになってました。

jug-action-based-mvcプロジェクトにテストコードを追加しました。

EmployeeDaoのテストコードを追加 · MasatoshiTada/jjug-action-based-mvc@2edd12b · GitHub

Payara Microの実行可能JAR(Uber JAR)がとても簡単な件

先日、Payara 4.1.1.162がリリースされました!

その組み込みサーバー版であるPayara Micro 4.1.1.162には、「Uber JAR」というSpring BootやWildFly Swarmのような単体で実行可能なJARを作る機能が追加されました。

Payara 4.1.1.162 がリリースされました - GlassFish Japan

従来のPayara Microでは、一旦アプリケーションを普通にWARにして、Payara Micro起動→WARをデプロイする必要がありました。

今回の新機能により、本当に単体で実行可能なJARを作れるように

手順は蓮沼さんのブログに書かれてある通りなのですが、自分でもやってみてあまりに簡単で感動したので、このブログにも書くことにしました。

手順

普通にWebアプリケーションを作る

本当に普通に作ってください。pom.xmlのpackageは「war」です。

組み込みじゃない据え置きのGlassFish/Payaraにデプロイするアプリケーションと全く同じ形で作ってください。

下記は、今月のJJUG CCC 2016 Springで紹介する予定の、MVC 1.0のサンプルアプリケーションです。

アプリケーション側は全く変えることなく、据え置きサーバー・実行可能JARの両方に対応できます。

jjug-action-based-mvc/jjug-mvc10 at master · MasatoshiTada/jjug-action-based-mvc · GitHub

そして、MavenでWARファイルにビルドします。

cd jug-mvc10
mvn clean package

jjug-mvc10/targetフォルダにjjug-mvc10-1.0-SNAPSHOT.warが作成されます。

実行可能JARを作成する

上記のWARファイルを元に、Payara Microで実行可能JARを作ります。

あらかじめ、Payara Micro 4.1.1.162のJARは下記からダウンロードし、適当なフォルダに保存してください。

Payara Server & Payara Micro - Downloads

そして、次のコマンドを実行します。

java -jar ~/Java/ap-server/payara-micro-4.1.1.162.jar\
 --deploy target/jjug-mvc10-1.0-SNAPSHOT.war\
 --outputUberJar target/jjug-mvc10.jar

--outputUberJarというオプションがポイントです。

こうすると、targetフォルダにjjug-mvc10.jarというJARファイルが作成されます。これが実行可能JARです。

ちなみに、このオプションがない場合、これまでのPayara Microと同じ挙動になります。

-jar の後は先ほどダウンロードしたPayara MicroのJARファイルのパス、--deployの後はWARファイルのパスです。

ちなみに、jar tf target/jjug-mvc10.jarとすると、実行可能JARの中身を確認することができます。

とても長いので全部は載せませんが、Payara Microの中身が解凍・再パッケージされていることが確認できます。

META-INF/services/javax.enterprise.inject.spi.CDIProvider
META-INF/services/org.glassfish.tyrus.core.ComponentProvider
META-INF/services/javax.management.remote.JMXConnectorProvider
(中略)
META-INF/
META-INF/MANIFEST.MF
__cp_jdbc_ra.rar
__dm_jdbc_ra.rar
__ds_jdbc_ra.rar
__xa_jdbc_ra.rar
META-INF/configuration/
META-INF/hk2-locator/
META-INF/maven/
META-INF/maven/org.glassfish.main.concurrent/
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/
org/
org/glassfish/
org/glassfish/concurrent/
org/glassfish/concurrent/config/
META-INF/configuration/context-service-conf.xml
META-INF/configuration/managed-executor-service-conf.xml
META-INF/configuration/managed-scheduled-executor-service-conf.xml
META-INF/configuration/managed-thread-factory-conf.xml
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/pom.properties
META-INF/maven/org.glassfish.main.concurrent/concurrent-connector/pom.xml
org/glassfish/concurrent/config/ConcurrencyResource.class
org/glassfish/concurrent/config/ContextService$ContextServiceConfigActivator.class
org/glassfish/concurrent/config/ContextService$Duck.class
org/glassfish/concurrent/config/ContextService.class
org/glassfish/concurrent/config/ContextServiceInjector.class
org/glassfish/concurrent/config/LocalStrings.properties
(以下略)

一番最後の方に、自分で作ったWARファイルが見えます。

...
META-INF/deploy/
META-INF/deploy/jjug-jersey-mvc-1.0-SNAPSHOT.war
META-INF/deploy/payaramicro.properties

META-INF/deployに直接WARファイルが入ってるんですね!

実行する

java -jar target/jjug-mvc10.jar

これで実行できます。

後はブラウザで開いてださい。

http://localhost:8080/jjug-mvc10-1.0-SNAPSHOT/

ちなみに、僕はここまでの処理は1つのシェルスクリプトにまとめて、実行する時はそれを叩くだけにしています。

jjug-action-based-mvc/build-jar.sh at master · MasatoshiTada/jjug-action-based-mvc · GitHub

まとめ

WARに固めてから実行可能JARにする、というのが少し違和感があるかもしれませんが、アプリケーション側に何も変更なく実行可能JARが出来るというのは非常に面白いと感じました。

ぜひご自分でも試してみてください!

Jersey MVCでThymeleafを使おう!

この記事について

JJUG CCC 2016 Spring「ネクストStruts/Seasr2としてのJava EEアクションベースMVC入門」の補足記事です。

Jersey MVCで、JSPの代わりのビューとしてThymeleafを使う方法を解説します。

Jersey MVCの基本については、下記の記事を参照してください。

Java EE 7でもアクションベースMVC! -MVC 1.0への移行を睨んだJersey MVCの活用- #javaee - Java EE 事始め!

環境

  • Payara Web ML 4.1.1.162
  • Thymeleaf 2.1.4.RELEASE

ソースコード

jjug-action-based-mvc/jjug-jersey-mvc at master · MasatoshiTada/jjug-action-based-mvc · GitHub

TemplateProcessorクラスを作る

Jersey MVCで公式にサポートされているビューはJSP/Freemarker/Mustacheの3種類です。

それ以外のビューを使うには、Jersey MVCで提供されているTemplateProcessorインタフェースを実装する必要があります。

Jersey MVCには、TemplateProcessorを実装したAbstractTemplateProcessorという抽象クラスがありますので、これを継承して作成します。

クラスの作成、リクエストとレスポンスの取得

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    @Inject
    private javax.inject.Provider<Ref<HttpServletRequest>> requestProviderRef;
    @Inject
    private javax.inject.Provider<Ref<HttpServletResponse>> responseProviderRef;
    // 後に続く

まず、AbstractTemplateProcessorを継承し、クラスには@Providerを付加します。

次に、フィールドインジェクションでHttpServletRequestHttpServletResponseを取得するのですが、@Providerが付加されたクラスはJAX-RSの仕様では、デフォルトでシングルトンと定められています。

なので、直接HttpServletRequestHttpServletResponseをインジェクションすると、スレッドセーフでない可能性があると考えました。

僕はスレッドセーフか否かといったあたりはあまり詳しくないので、直接取得でも大丈夫な可能性もありますが・・・。

そこで、Jersey MVCJSPTemplateProcessorである下記のクラスを参考に作りました。

jersey/JspTemplateProcessor.java at 2.x · jersey/jersey · GitHub

これで本当にスレッドセーフになっているのかは、僕には判断がつかないので、この点はご自分で判断してください。

TemplateEngineの作成

    // 続き
    private TemplateEngine templateEngine;

    @Inject
    public ThymeleafTemplateProcessor(Configuration config, ServletContext servletContext) {
        super(config, servletContext, "html", "html");
        TemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix((String) config.getProperty(MvcFeature.TEMPLATE_BASE_PATH));
        // setSuffix()は指定しない
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
    }
    // 後に続く

フィールドにTemplateEngine(Thymeleafのクラス)を宣言し、コンストラクタ内で初期化します。

ポイントは、setSuffix()という、通常は拡張子を指定するメソッドを呼んでいないことです。

これは、MVC 1.0との互換性のために、コントローラーメソッド拡張子を指定したいからです。

メソッドのオーバーライド

    // 続き
    @Override
    protected String resolve(String templatePath, Reader reader) throws Exception {
        return templatePath;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();
        httpServletResponse.setCharacterEncoding("UTF-8");
        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> variables = (Map<String, Object>) model;
            webContext.setVariables(variables);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", model);
            webContext.setVariables(variables);
        }
        try (Writer writer = new OutputStreamWriter(out)) {
            templateEngine.process(viewable.getTemplateName(), webContext, writer);
        }
    }
}

resolve()は、引数のtemplatePathをそのまま返せばOKです。

writeTo()が、最終的にレスポンスを書き込むメソッドです。

webContext.setVariables()は、ビューに渡す値をマップ形式で指定します。

コントローラーメソッドViewableに渡された値は、viewable.getModel()で取得できます。

この値がMapだった場合は、それを直接webContext.setVariables()に渡し、そうでない場合はJersey MVCデフォルトのmodelという名前でMapにputしています。

こうすることで、ビューから値を参照するときに、${model.employee.id}のようにmodelを付けなくてもいいようにしています。

writeTo()の最後では、templateEngine.process()を呼んでいます。これが、Thymeleafのレスポンスを書き出しています。

自作Viewableクラスの作成

これはThymeleafを使う上では必須ではないのですが、MVC 1.0との差異をなるべく吸収するために作りました。

前述のブログの通り、Jersey MVCには「/」で始める絶対パスと「/」で始めない相対パスがあり、それぞれビューの保存フォルダが異なります。

ビューの保存フォルダがMVC 1.0とほぼ同じなのは絶対パスですが、MVC 1.0(というか現在のOzark)では「/」で始めるとコンテキストルートからのパスになってしまいます。

なので、「/」で始めない相対パスで指定しつつ、かつビューの保存フォルダが絶対パスになるように、Viewableのサブクラスを自作しました。

public class ThymeleafViewable extends Viewable {

    public ThymeleafViewable(String templateName) throws IllegalArgumentException {
        super(templateName);
    }

    public ThymeleafViewable(String templateName, Map<String, Object> models) throws IllegalArgumentException {
        super(templateName, models);
    }

    @Override
    public Map<String, Object> getModel() {
        return (Map<String, Object>) super.getModel();
    }

    @Override
    public String getTemplateName() {
        String templateName = super.getTemplateName();
        if (templateName.startsWith("/") == false) {
            return "/" + templateName;
        } else {
            return templateName;
        }
    }

    @Override
    public boolean isTemplateNameAbsolute() {
        return true;
    }
}

コードを読んでいただければ大体わかると思いますが、getTemplateName()が必ずスラッシュで始まるパス(=絶対パス)を返すようにし、かつisTemplateNameAbsolute()trueを返して、Jersey MVC絶対パスと認識させます。

また、コンストラクタはビューのパスとMapを引数で受け取るようにします。

コントローラーメソッド

    @GET
    @Path("index")
    public ThymeleafViewable index() throws Exception {
        return new ThymeleafViewable("employee/index.html");
    }
    @GET
    @Path("result")
    public ThymeleafViewable result(@BeanParam EmployeeIdForm form) throws Exception {
        Integer id = Integer.valueOf(form.getId());
        Employee employee = employeeService.findByEmpId(id).orElse(null);
        HashMap<String, Object> models = new HashMap<>();
        models.put("employee", employee);
        return new ThymeleafViewable("employee/result.html", models);
    }

パスは「/」で始めない、MVC 1.0と同じ形式にしています。

また、拡張子「.html」を明示的に指定しています。これもMVC 1.0と同じです。

ビュー

<p th:if="${employee == null}">該当する社員はいませんでした。</p>
<table border="1" th:unless="${employee == null}">
    <tr><th>社員ID</th><th>氏名</th><th>入社年月日</th><th>部署ID</th><th>部署名</th></tr>
    <tr th:object="${employee}">
        <td th:text="*{empId}">99999</td>
        <td th:text="*{name}">Taro Yamada</td>
        <td th:text="*{joinedDate}">2020-01-01</td>
        <td th:text="*{department.deptId}">99</td>
        <td th:text="*{department.name}">Admin</td>
    </tr>
</table>

modelを指定せず、直接employeeで値を参照していることが分かります。

これにより、Jersey MVCからMVC 1.0に変更した際に、ビューを全く変更しなくて済むようにしています。

まとめ

TemplateProcessorと自作Viewableにより、極力MVC 1.0と同じ感覚で、ビューとコントローラーを実装できるようにしました。

参考にしたWebサイト

@bufferingsさんのブログ。

jersey-thymeleaf using ViewProcessor - Mitsuyuki.Shiiba

@backpaper0さんのコード。

sealion/ThymeleafProvider.java at master · backpaper0/sealion · GitHub

Jersey MVC + Thymeleafしたら文字化けた時の対策

環境

  • OS X 10.11.4
  • Google Chrome
  • Payara Web ML 4.1.1.161.1 (Jersey 2.22.1)
  • Thymeleaf 2.1.4.RELEASE

プログラム概要

@bufferingさんの記事を参考に、Thymeleaf用のTemplateProcessorを作成。

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    @Inject
    private javax.inject.Provider<Ref<HttpServletRequest>> requestProviderRef;
    @Inject
    private javax.inject.Provider<Ref<HttpServletResponse>> responseProviderRef;

    private TemplateEngine templateEngine;

    @Inject
    public ThymeleafTemplateProcessor(Configuration config, ServletContext servletContext) {
        super(config, servletContext, "html", "html");
        TemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix((String)config.getProperty(MvcFeature.TEMPLATE_BASE_PATH));
        templateResolver.setSuffix(".html");
        templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
    }

    @Override
    protected String resolve(String templatePath, Reader reader) throws Exception {
        return templatePath;
    }

    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();
        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> map = (Map) model;
            webContext.setVariables(map);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", viewable.getModel());
            webContext.setVariables(variables);
        }
        templateEngine.process(viewable.getTemplateName(), webContext, 
                httpServletResponse.getWriter());
    }

}

リソースクラス。

@Path("employee")
@RequestScoped
@Produces(MediaType.TEXT_HTML)
public class EmployeeResource {
    
    @GET
    @Path("index")
    public Viewable index() throws Exception {
        return new Viewable("/employee/index");
    }
}

Thymeleafのビュー。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>入力画面</title>
    <link rel="stylesheet" href="../../css/style.css"/>
</head>
<body>
<p>社員IDを入力してください(77, 88, 99で例外発生)</p>

<!-- 検証エラーメッセージの表示 -->
<ul class="error" th:if="${violations != null}">
    <li th:each="violation : ${violations}" th:text="${violation.message}">ダミーのメッセージ</li>
</ul>

<form action="./result" method="get">
    社員ID:<input type="text" name="id" value="" th:value="${param.id == null} ? '' : ${param.id[0]}"/>
    <input type="submit" value="検索"/>
</form>
</body>
</html>

現象

画面の全日本語メッセージが文字化けている。

f:id:MasatoshiTada:20160501145927p:plain

原因と対策

レスポンスヘッダーやブラウザが現在開いている文字コードを見ると、正しくUTF-8になっていた。

よって、そもそもレスポンスを書き込む際に文字コードが正しく設定されていないっぽい。

TemplateProcessorにhttpServletResponse.setCharacterEncoding("UTF-8");を追加したら、正しく表示された。

f:id:MasatoshiTada:20160501150201p:plain

@Provider
public class ThymeleafTemplateProcessor extends AbstractTemplateProcessor<String> {

    // 中略
    
    @Override
    public void writeTo(String templateReference, Viewable viewable, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream out) throws IOException {
        HttpServletRequest httpServletRequest = requestProviderRef.get().get();
        HttpServletResponse httpServletResponse = responseProviderRef.get().get();

        // これを追加
        httpServletResponse.setCharacterEncoding("UTF-8");

        WebContext webContext = new WebContext(
                httpServletRequest, httpServletResponse, 
                super.getServletContext(), httpServletRequest.getLocale());
        Object model = viewable.getModel();
        if (model instanceof Map) {
            Map<String, Object> map = (Map) model;
            webContext.setVariables(map);
        } else {
            Map<String, Object> variables = new HashMap<>();
            variables.put("model", viewable.getModel());
            webContext.setVariables(variables);
        }
        templateEngine.process(viewable.getTemplateName(), webContext, 
                httpServletResponse.getWriter());
    }

}

ちなみに

Ozark + Thymeleafだと、この記述が無くても正しく表示された。

(ViewEngineクラスにもそれらしい記述は無い)

理由は不明。。。

Cloud Foundryワークショップに参加してきました! #cfws

2016-04-05(火)に開催された、Pivotalさん主催のCloud Foundryワークショップに参加してきました!

クラウド関連のことはあまり知識がないので、クラウドの世界ってどんなものなんだろう?ということ知りたかったというのが動機です。

講師は@makingさん。冒頭は、3月のJJUGナイトセミナーの資料でCloud Foundryの概要説明でした。

理解できた範囲で要約すると、

  • IaaS上にPaaSを構築・管理するためのソフトウェア群
  • Pivotal社単独ではなく、「Cloud Foundry Foundation」という団体が仕様を策定している
  • Pivotal Web Servicesは、AWS上にCloud FoundryでPaaSを構築済みのもの。アカウントを作成すればすぐに利用できる
  • PCF Devは、ローカルPC上にCloud Foundry環境を構築するもの
  • Cloud Foundry上へのアプリケーションのデプロイなどは、CLIツールをインストールしてcfコマンドで行う

その他、Cloud Foundry内部の仕組みや、Blue-Green Deployなどの説明もありました。 (なんか理解が間違ってたらツッコミください・・・)

その後、下記の資料でハンズオン。

GitHub - Pivotal-Japan/cf-workshop: Cloud Foundry Workshop

Cloud Foundryワークショップ資料 - Qiita

(上記のGitHubとQiitaは、両方同じ資料です)

特に印象深かったのは、「5. スケールアウト」と「7. Blue-Greenデプロイ」です。

例えば、より多くのクライアントに対応するためにインスタンス数を増やして負荷分散したいという場合は、ローカルPCから下記のコマンドを叩くだけ。

$ cf scale -i 4 hello-redis-tada

上記だとインスタンス数が4になります。かかった時間も、ものの数秒でした。で、ロードバランシングとかはCloud Foundry内のRouterがよろしくやってくれる、と。

Blue-Greenデプロイは、名前だけは聞いたことがあったものの、どんなものかはよく知りませんでした。

新しいバージョンのアプリケーションをデプロイする際に、ダウン時間をなるべくゼロに近づけるための手法だと理解しました。

これも、Cloud Foundryを使えば非常に簡単ですね。

クラウドって実際に触ったことがない」という人にはとてもオススメのワークショップです!

これから何回か開催予定ということなので、是非参加してみてはいかがでしょうか。

ハンズオンはボリュームがそこそこありますので、当日は余裕を持って話を聞きたいという方は「3. 簡単なアプリケーションをデプロイ」くらいまでは事前に予習しておくといいかもしれません。

TOEIC受験記(2016年3月)

なぜ受験しようと思ったか

僕の仕事は研修講師(主にプログラミング)なので、色々と調べ物をする時にどうしても英語の仕様書・書籍・ブログ記事などを読む必要が出てきます。やはり、ITの本場はアメリカです。

なので、もっとリーディング力を高めたいと思ったのがきっかけです。

勉強前の英語レベルは?

中学までは英語は得意科目でしたが、高校になるとマークシート形式ならなんとか、という感じでした(センター試験だと8〜9割くらい)。でも、記述式はまったくダメでした。

10年くらい前、まだ大学生の頃、試しにTOEICを受けたことがありましたが、たしか440〜450点だったと思います。

結果は?

Listening Reading Total
395 430 825

勉強法は?

僕の勉強法はいたってシンプルで、市販書籍でインプットとアウトプットをひたすら繰り返すのみです。

勉強時間は、主に通勤時間と土日です。家族に感謝。

改訂版 TOEIC(R) TEST 英文法 出るとこだけ!

Amazon.co.jp | 音声DL付 改訂版 TOEIC(R) TEST 英文法 出るとこだけ! | 本 ・TOEIC 通販

まずはじめに読んだ本です。文法のインプット・アウトプットに使いました。ページは少ないけど文法を理解するコツがしっかりと解説されていて、記憶力も根気も無い自分にはピッタリの本でした。あえて受験テクニック的な見出しが付いていますが、文法的な背景も解説されています。

僕は暗記物がとても苦手なので、いきなり単語集をやると確実に挫折します。というか、実際に挫折しました・・・。なので、文法で理論的な部分をある程度身につけてから、単語に取り掛かりました。その方がモチベーションも続きます。

この本は3回くらい繰り返しました。

改訂版 TOEIC(R)TEST 英単語 出るとこだけ!

Amazon.co.jp | CD-ROM付 改訂版 TOEIC(R)TEST 英単語 出るとこだけ! | 本 ・TOEIC 通販

文法の勉強をしたはいいが、やはり単語が分からないことにはリーディングができないことに気付き、文法の本と同じ小石裕子さんの本を読みました。

TOEICは出る単語が(文法もですが)決まっている感じがするので、TOEICを受けるならTOEIC専用の単語集を使ったほうがいいと思います。

この本は、5回くらい繰り返しました。

TOEICテスト公式プラクティス リーディング編

Amazon.co.jp | TOEICテスト公式プラクティス リーディング編 | 本 ・TOEIC 通販

TOEICを実施しているETSが出している、公式の練習問題集です。これは2回くらい繰り返しました。

改訂版 TOEIC(R) TEST リスニング 出るとこだけ!

Amazon.co.jp | CD付 改訂版 TOEIC(R) TEST リスニング 出るとこだけ! | 本 ・TOEIC 通販

今回はリーディングに絞って勉強するつもりだったのですが、時間に少し余裕が出来たので、直前の2週間だけリスニングの勉強をしました。

読んだのは、やはり小石裕子さんの本です。これは1回通りやるのが精一杯でした。

TOEICテスト新公式問題集< Vol.6>

Amazon.co.jp | TOEICテスト新公式問題集< Vol.6> | 本 ・TOEIC 通販

本番の試験と同じ形式の模試が、2回分入っています。1回目は普通に問題集として解き、2回目を実際の時間(2時間)を測って解きました。

下記サイトによると、Vol.6が最も本番の難易度に近いそうです。 僕はこのVol.6しか解いていませんが、確かに本番の試験と難易度は似ていると感じました。

TOEIC 公式問題集はどれから解けば良いの?

勉強してどうだったか

英語の文書を読むのが少し楽になったように感じます。

これは、勉強したことにより地力が鍛えられたこともありますが、スコアを取ったことによる自信も大きいと思います。

勉強する→結果が出る→自信がつく→モチベーションが湧く→勉強する→・・・

このサイクルを回し続けることが肝心です。

今後、また受験するかは未定ですが、英語文書をより読んでいって更に英語力を鍛えていこうと考えています。

使ってないけどやっておいたほうが良かったかなと思う本

TOEICテスト公式プラクティス リスニング編

Amazon.co.jp | TOEICテスト公式プラクティス リスニング編 | 本 ・TOEIC 通販

公式プラクティスのリスニング版です。今回は時間の都合上やりませんでした。

TOEICテスト 公式問題で学ぶボキャブラリー

Amazon.co.jp | TOEICテスト 公式問題で学ぶボキャブラリー | 本 ・TOEIC 通販

これも時間の都合上やりませんでした。

TOEICテスト新公式問題集〈Vol.4〉

Amazon.co.jp | TOEICテスト新公式問題集〈Vol.4〉 | 本 ・TOEIC 通販

前述のサイトによると、公式問題集の中でも一番難しいものだそうです。ハイスコアを目指す人はやっておくといいかもしれません。

改訂版 TOEIC(R) TEST 文法・語彙出るとこだけ! 問題集

Amazon.co.jp | 音声DL付 改訂版 TOEIC(R) TEST 文法・語彙出るとこだけ! 問題集 | 本 ・TOEIC 通販

これも時間の都合上やりませんでした。

TOEICテスト中学英文法で600点!

Amazon.co.jp | 新TOEICテスト中学英文法で600点! | 本 ・TOEIC 通販

中学英語に自信が無い人は、これから始めるといいかもしれません。

Jersey MVCのレスポンスのContent-Typeが「*/*」になる問題対策

詳細は後ほど追記します。

ContianerResponseFilterだと何故かできなかった・・・。

Jersey MVCは、MessageBodyWriterの中でContent-Typeを上書きしています。

ContianerResponseFilterはMessageBodyWriterよりも前に実行されるので、そりゃあ効かないですね・・・。

なので、必ずWriterInterceptorの「後処理」として書く必要があります。

MessageBodyWriterでなぜ「*/*」に上書きされるのかは、現在ソースを読んで調査中です。

package com.example.rest.interceptor;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;

@Provider
public class ContentTypeWriterInterceptor implements WriterInterceptor {
    @Override
    public void aroundWriteTo(WriterInterceptorContext context) 
            throws IOException, WebApplicationException {
        // デバッグログ
        System.out.println("======== BEFORE : " + context.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
        // レスポンスへの書き込み実行(必須)
        context.proceed(); 
        // デバッグログ
        System.out.println("======== AFTER : " + context.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE));
        // レスポンスヘッダーに "Content-Type: text/html"を上書きする
        MultivaluedMap<String, Object> headers = context.getHeaders();
        headers.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML);
    }
}