sinsengumi血風録
HttpServletRequestWrapperを使って、HTTPリクエストを改変する
- 2012年4月14日 4:42 PM
- Java
OpenAMは、認証済のユーザー情報をHTTPリクエストヘッダーに載せてくれるんですが、Servletでそれってどうやって実現してるのかなーと思って、調べたら HttpServletRequestWrapper クラスを使うようです。
使い方ですが、HttpServletRequestWrapper は、HttpServletRequestインタフェースを実装しているので、HttpServletRequestインタフェースのメソッドをオーバーライドして独自の実装を定義します。
http://mergedoc.sourceforge.jp/tomcat-servletapi-5-ja/javax/servlet/http/HttpServletRequestWrapper.html
今は、すべてのリクエストのヘッダーに情報を付与しようとしているので、getHeader辺りをオーバーライドします。
package org.sample;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class HeaderAddedHttpServletRequest extends HttpServletRequestWrapper {
private Map<String, String> addHeaders;
public HeaderAddedHttpServletRequest(HttpServletRequest request, Map<String, String> addHeaders) {
super(request);
if (addHeaders == null) {
this.addHeaders = new HashMap<String, String>();
} else {
this.addHeaders = addHeaders;
}
}
@Override
public String getHeader(String name) {
String header = super.getHeader(name);
if (header != null) {
return header;
}
header = addHeaders.get(name);
if (header != null) {
return header;
}
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
List<String> newHeaderNames = new ArrayList<String>();
Enumeration<String> currentHeaderNames = super.getHeaderNames();
while (currentHeaderNames.hasMoreElements()) {
newHeaderNames.add(currentHeaderNames.nextElement());
}
for (Entry<String, String> e : addHeaders.entrySet()) {
newHeaderNames.add(e.getKey());
}
return Collections.enumeration(newHeaderNames);
}
}
で、すべてのリクエストがこのWrapperクラスを使うようにFilterを定義します。
package org.sample;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter("/*")
public class AddHeaderFilter implements Filter {
@Override
public void init(FilterConfig fConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Map<String, String> addHeaders = new HashMap<String, String>();
addHeaders.put("X-USER-ID", "HIJIKATA");
addHeaders.put("X-USER-NAME", "土方歳三");
chain.doFilter(new HeaderAddedHttpServletRequest((HttpServletRequest) request, addHeaders), response);
}
}
これで、HTTPリクエストが飛んできたら自動でヘッダーが付与サれてます。
package org.sample;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/SampleServlet")
public class SampleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
System.out.println(headerName + "=" + headerValue);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
}
host=localhost:8080 connection=keep-alive user-agent=Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.152 Safari/535.19 accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 accept-encoding=gzip,deflate,sdch accept-language=ja,en-US;q=0.8,en;q=0.6 accept-charset=Shift_JIS,utf-8;q=0.7,*;q=0.3 X-USER-ID=HIJIKATA X-USER-NAME=土方歳三
同様の事はレスポンスでもできます。
- Comments: 0
- Trackbacks: 0
シンプルな HTTP Server 書いた
- 2012年3月31日 3:40 PM
- Java
以前の記事の続きということで、シンプルな HTTP Server を書いた。
classファイル一つで動くというお手軽サーバー。マルチスレッド?自分しか使わないからいいんですよ。
お手軽に使いたいのでデフォルトパッケージ。
java SimpleHttpServer "C:\htdocs" 81
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class SimpleHttpServer {
private static String requestPath;
private static Map<String, String> CONTENT_TYPES = new HashMap<String, String>();
static {
CONTENT_TYPES.put("html", "text/html");
CONTENT_TYPES.put("htm", "text/html");
CONTENT_TYPES.put("css", "text/css");
CONTENT_TYPES.put("js", "text/javascript");
CONTENT_TYPES.put("jpg", "image/jpeg");
CONTENT_TYPES.put("jpeg", "image/jpeg");
CONTENT_TYPES.put("png", "image/png");
CONTENT_TYPES.put("gif", "image/gif");
CONTENT_TYPES.put("pdf", "application/pdf");
CONTENT_TYPES.put("txt", "text/plain");
CONTENT_TYPES.put("xml", "text/xml");
CONTENT_TYPES.put("zip", "application/zip");
CONTENT_TYPES.put("exe", "application/octet-stream");
}
public static void main(String[] args) throws IOException {
String documentRoot = args[0];
int port = Integer.parseInt(args[1]);
ServerSocket server = new ServerSocket(port);
while (true) {
Socket client = server.accept();
// HTTPリクエストを出力
outputRequest(client);
// HTTPレスポンスを出力
outputResponse(client, documentRoot);
}
}
private static void outputRequest(Socket client) throws IOException {
System.out.println("------------------------------------------");
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 1行目からリクエストパスを取得しておく(/でアクセスされた場合はindex.htmlを表示)
String inline = br.readLine();
requestPath = inline.split(" ")[1];
if (requestPath.endsWith("/")) {
requestPath += "index.html";
}
while (br.ready() && inline != null) {
System.out.println(inline);
inline = br.readLine();
}
System.out.println("------------------------------------------");
}
private static void outputResponse(Socket client, String documentRoot) throws IOException {
PrintStream ps = new PrintStream(client.getOutputStream());
String responseFile = documentRoot + requestPath;
File file = new File(responseFile);
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
// ヘッダー出力
int bodyLength = (int) file.length();
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Length: " + bodyLength);
ps.println("Content-Type: " + getContentType(file));
ps.println("");
// ボディ出力
byte buf[] = new byte[bodyLength];
fis.read(buf);
ps.write(buf, 0, bodyLength);
ps.flush();
} catch (FileNotFoundException e) {
// ヘッダー出力
ps.println("HTTP/1.1 404 Not Found");
ps.println("");
ps.println("<h1>指定されたファイルは存在しません。</h1>");
} finally {
if (fis != null) {
fis.close();
}
}
ps.close();
}
private static String getContentType(File file) {
String extension = getExtension(file);
String contentType = CONTENT_TYPES.get(extension);
if (contentType == null) {
return "text/html";
} else {
return contentType;
}
}
private static String getExtension(File file) {
String path = file.getPath();
int lastDotPosition = path.lastIndexOf(".");
String extension = null;
if (lastDotPosition == -1) {
extension = "";
} else {
extension = path.substring(lastDotPosition + 1).toLowerCase();
}
return extension;
}
}
コードはGistにもペタリ。
https://gist.github.com/2260004
- Comments: 0
- Trackbacks: 0
Socket の勉強のためにエコーサーバー書いた
- 2012年3月21日 2:05 PM
- Java
前々から、Jarひとつで動くHTTPサーバーを書いてみたいと思っていて(既にたくさんあると思うけど)、それには Socket でがちゃがちゃしないといけないとの事だったので、重い腰を上げて Socket の勉強をしてみた。
とりあえず、リクエストされた文字列をそのまま返すだけのエコーサーバーを書いてみた。
一応、マルチスレッド対応。
Server
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class EchoServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(5000);
// クライアントからの接続を待ち受ける
while (true) {
Socket client = server.accept();
new Thread(new EchoServer().new EchoThread(client)).start();
}
}
class EchoThread implements Runnable {
Socket client;
public EchoThread(Socket client) {
this.client = client;
}
@Override
public void run() {
echo(client);
}
private void echo(Socket client) {
System.out.println("処理開始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("接続中 : " + client.getRemoteSocketAddress());
try {
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
int recvByte;
while ((recvByte = in.read()) != -1) {
out.write(recvByte);
}
} catch (IOException e) {
System.err.println(e);
} finally {
closeQuietly(client);
}
System.out.println("処理終了");
}
private void closeQuietly(Socket socket) {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// ignore
}
}
}
}
Client
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class EchoClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 5000);
System.out.println("サーバとの接続を確立 : " + socket.getLocalPort());
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] data = args[0].getBytes();
out.write(data);
System.out.println("送信 : " + new String(data));
int i = 0;
byte[] output = new byte[data.length];
int recvByte;
while ((recvByte = in.read()) != -1) {
output[i++] = (byte) recvByte;
if (i >= data.length) {
break;
}
}
System.out.println("受信 : " + new String(output));
socket.close();
}
}
バイトの読み書きを1バイトずつやってるので、あまり効率的ではないんだろうな。
- Comments: 0
- Trackbacks: 1
OpenAM(旧OpenSSO)でシングルサインオンをしてみる
- 2012年3月14日 1:20 PM
- その他
やりたいことは、クロスドメイン(CDSSO)でシングルサインオンを実現し、ユーザーを識別(認可)して、アクセスできるURLを制限したい、というもの。
SSO製品は色々あるようですが、一番とっつきやすそうなOpenAMで試す。
SSOエージェントなど、独特のSSO用語は以下のページがとてもわかり易いです。
http://dev.ariel-networks.com/column/tech/opensso/
前提
SSOサーバーとSSOエージェントは別マシン(同じマシンで実行した(もちろんポートを変えて)が上手くいかなかった為)
- SSOサーバーのホスト(10.29.56.64) : sso.server.com
- SSOエージェントのホスト(10.29.55.56) : sso.agent.com
SSOはCookieを用いて、SSO Tokenをやり取りするので、localhostなどでアクセスして設定を行なってしまうと、Cookieのドメイン属性がうまく設定できず、動かない可能性がある。
そのため、設定を行う場合は必ずFQDNでアクセスする。
試しにやる場合は、hostsファイルを編集するのが一番楽かと思います。
hosts
10.29.56.64 sso.server.com 10.29.55.56 sso.agent.com
なお、このhosts設定はSSOサーバーのマシン、SSOエージェントのマシン両方に設定しておく必要がある(どちらもそれぞれのホストを見に行く必要があるため)
環境
- Tomcat 6.0.35(SSOサーバーマシン用)
TOMCAT_HOME=C:\Program Files\apache-tomcat-6.0.35-server - Tomcat 6.0.35(SSOエージェントマシン用)
TOMCAT_HOME=C:\Program Files\apache-tomcat-6.0.35-agent - Windows XP SP3
- OpenAM(openam_954.war)
- J2EE Policy Agent(tomcat_v6_agent_3.zip)
今回は、JavaEEサーバーにエージェントを組み込む、「エージェント型」で動かしてみる。
また、OpenSSOを組み込むTomcat(SSOサーバー用)とSSOエージェントを組み込むTomcat(SSOエージェント用)は、必ず別にする必要がある(SSOエージェントをインストールする際に、SSOサーバー用Tomcatは起動していて、SSOエージェント用Tomcatは停止している必要があるため)
手順
- OpenAMをインストール
- OpenAMにプロファイルを作成
- SSOエージェントをインストール
- SSO対象のWebアプリにフィルタを設定
- OpenAMにポリシーを作成
- 確認
1. OpenAMをインストール
SSOサーバーとして機能するOpenAMは、JavaのWebアプリとして配布されているので、配備はWARをデプロイするだけです。
以下から、WAR(openam_954.war)をダウンロードする。
http://www.forgerock.org/openam.html
WARを以下のように配置する(openam_954.war を openam.war にリネームした)
C:\Program Files\apache-tomcat-6.0.35_server\webapps\openam.war
Tomcatを起動したら、以下にアクセスする。
http://sso.server.com:8080/openam
以下の画面が出れば正常にデプロイされている。
「デフォルト設定の作成」を選択する。
パスワードは、以下。
- amAdmin : password1
- UrlAccessAgent : password2
インストールが始まる。
正常に完了したら、「ログインに進む」をクリックする。
「amAdmin」ユーザのID、パスワードを入力すればログインできる(IDは大文字・小文字を区別しないようだ)
ログイン成功!
2. OpenAMにプロファイルを作成
引き続きそのままプロファイルを作成する。
プロファイルは~~みたいなものだと思います。
[アクセス制御]タブ → [/ (最上位のレルム)] → [エージェント]タブ → [J2EE]タブ に移動する。
「エージェント」の「新規…」ボタンをクリックして、プロファイルを作成する。
- 名前 : tomcatagent
- パスワード : password3
- 設定 : 集中
- サーバーURL : http://sso.server.com:8080/openam
- エージェントURL : http://sso.agent.com:8080/agentapp
名前、パスワード、サーバーURL、エージェントURLは以降でも使うので、覚えておく。
ついでに、クロスドメインの設定もしておく
先ほど作成した、[tomcatagent] → [SSO]タブ → [クロスドメインSSO] に移動し、「有効」のチェックを付け、ページ上部の「保存」ボタンを押下する。
3. SSOエージェントをインストール
(SSOエージェント用マシンで実施)
まず以下から、ZIPをダウンロードし、解凍する。
http://www.forgerock.org/openam.html
場所は以下にした(この場所をエージェントが参照しているようなので、日本語パス等は避けたほうがいいかも)
C:\work\SSO\j2ee_agents
次に、プロファイルで設定したパスワードをパスワードファイルとして保存しておく必要がある。
以下に、「pass」というファイル名でパスワードファイルを作成した(場所はどこでもいいと思う)
C:\Program Files\apache-tomcat-6.0.35-agent\agentpass\pass
そして、このファイルにパスワードを平文で記述する(ここでは「password3」)
これで、準備が整ったので以下を実行する(SSOサーバー用のTomcatを起動しておくこと)
C:\work\SSO\j2ee_agents\tomcat_v6_agent\bin\agentadmin.bat --install
- Enter the Tomcat Server Config Directory Path→ C:\Program Files\apache-tomcat-6.0.35-agent\conf
- OpenSSO server URL→ http://sso.server.com:8080/openam
- Enter the $CATALINA_HOME environment variable→ C:\Program Files\apache-tomcat-6.0.35-agent
- Install agent filter in global web.xml ?→ false
- Agent URL→ http://sso.agent.com:8080/agentapp
- Enter the Agent Profile name→ tomcatagent
- Enter the path to the password file→ C:\Program Files\apache-tomcat-6.0.35-agent\agentpass\pass
あと、忘れずに、以下のフォルダにある「agentapp.war」をエージェント用のTomcatに配備(C:\Program Files\apache-tomcat-6.0.35-agent\webapps)しておく。
C:\work\SSO\j2ee_agents\tomcat_v6_agent\etc
4. SSO対象のWebアプリにフィルタを設定
これで、SSOを行う準備が整ったので、SSO対象のWEBアプリへのリクエストがエージェントを経由するように、WEBアプリのweb.xmlにフィルタを作成する。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>SampleWeb2</display-name>
<filter>
<filter-name>Agent</filter-name>
<filter-class>com.sun.identity.agents.filter.AmAgentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Agent</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>Sample1</servlet-name>
<servlet-class>org.sample.Sample1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Sample1</servlet-name>
<url-pattern>/Sample1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Sample2</servlet-name>
<servlet-class>org.sample.Sample2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Sample2</servlet-name>
<url-pattern>/Sample2</url-pattern>
</servlet-mapping>
</web-app>
大事なところはフィルタ「Agent」です。これでこのアプリへの全てのリクエストがエージェントを経由することになる。
ちなみにサンプル用のWEBアプリは以下のような構成。
| アプリケーション | アクセスパス | アクセスできるユーザー |
|---|---|---|
| SampleWeb1 | /SampleWeb1/Sample1 | sample0, sample1 |
| /SampleWeb1/Sample2 | sample0, sample1 | |
| SampleWeb2 | /SampleWeb2/Sample1 | sample0, sample2 |
| /SampleWeb2/Sample2 | sample0, sample2 |
5. OpenAMにポリシーを作成
最後に、OpenAMにどのURLをどのユーザに許可するなどのポリシーを作成する。
まずユーザーを作成しておく。
[アクセス制御]タブ → [/ (最上位のレルム)] → [対象]タブ で「新規…」ボタンを押下し、ユーザーを作成する。
今回は先ほど書いてるように「sample0, sample1, sample2」を作成した。
次にポリシーを作成する。
[アクセス制御]タブ → [/ (最上位のレルム)] → [ポリシー]タブ で「新規ポリシー…」ボタンを押下する。
一般
名前 : SampleWeb1
ルール
サービスタイプ : URL ポリシーエージェント (リソース名あり)
名前 : Sample1
リソース名 : http://sso.agent.com:8080/SampleWeb1/Sample1
アクション : GET,POST(共に許可)
同様にして「/SampleWeb1/Sample2」を許可するルールも追加しておく。
対象
タイプ : OpenAM アイデンティティー対象
名前 : identity1
sample0, sample1 を選択
SampleWeb2用の新規ポリシーも上記と同様にして作成する。
6. 確認
最後に正しく、SSOとアクセス制御が出来ているか確認する。
エージェントが組み込まれたTomcatが起動していない場合は起動する。
http://sso.agent.com:8080/SampleWeb1/Sample1 にアクセス。未ログインなので、ログイン画面が表示される。
「sample1」でログインすると、正常にアプリにアクセスできる。
http://sso.agent.com:8080/SampleWeb2/Sample1 にアクセス。権限が無いので、403エラーになる。

一旦、ログアウト(http://sso.server.com:8080/openam/UI/Logout にアクセス)して、
再度、http://sso.agent.com:8080/SampleWeb1/Sample1 にアクセス。今度は「sample0」でログインする。
「sample0」は別アプリの /SampleWeb1 と /SampleWeb2 にそれぞれアクセスできる。
- Comments: 0
- Trackbacks: 0
なぜ Java の配列は共変で、Generics は共変ではないのか
- 2011年12月12日 11:42 PM
- Java
まず、Java の配列がタイプセーフではない話。
public class ExampleArray {
public static void main(String[] args) {
String[] strArray = {"test1", "test2"};
Object[] objArray = strArray; // 配列は共変なので代入可能
objArray[0] = new Integer(3); // java.lang.ArrayStoreException
}
}
上記のように、Java の配列は共変という性質を持っているので、Object[] に String[] を代入することができます。つまり、String[] は Object[] のサブクラスである、ということです。
しかし、Generics の場合は、この性質が当てはまりません(Generics は共変ではない)
public class ExampleGenerics {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
strList.add("test1");
strList.add("test2");
List<Object> objList = new ArrayList<Object>();
objList = strList; // コンパイルエラー
}
}
なぜ、配列は共変で、Generics は共変ではないのか?(一緒の方が分かりやすいのに)
その理由の前に、
配列の例で見たように、型の混入現象は、強い型付け言語である Java としては避けたい現象です。
配列の場合は、不正な型が混入した場所で例外(java.lang.ArrayStoreException)を投げてくれます。
(これは、結構大事な所で、使用時(get)ではなく、設定時(set)にきちんと例外を投げてくれると、バグの混入場所が特定しやすいです)
なぜこんなことができるかというと、配列は自分が何の型であるかを自身で知っている(バイトコードに型情報が存在する)ので、違う型を入れたときに、自分と違うということが判定できるのです。
一方、Generics の場合はコンパイル時に型消去という操作が行われます。
型消去については、別エントリーでも書いてます(Generics(Java)の型消去について)
Generics のコードは、コンパイル後にはその型情報を一切残していないので、java.lang.ArrayStoreException のような例外を投げることができません。
そのため、共変ではなくして、型の混入を防いでいるのではないかと思います。
Generics で共変っぽいことをしたい場合は、extends とかの境界条件をつければ可能です。
- Comments: 0
- Trackbacks: 0
JPA Hibernate (H2 database) をやってみる
- 11:37 AM
- Java
最近、Play framework が面白いなぁと思ってちょろちょろ遊んでたりするんですが、Play はモデル層に JPA を採用していて、実は JPA って一度も触った事が無かったのでこれを機にどういうものなのか試して見ました。
プロジェクトの準備
JPA は昔は Java EE(EJB) でしか使えなかったらしいですが、スタンドアロンでも使えるようになったらしいので、スタンドアロンで試してみます。
JPA の実装は Hibernate を使用します。
この Hibernate がやたら依存モジュールが多いので、簡単のため maven プロジェクトにします。
mvn archetype:create -DgroupId=com.example -DartifactId=JPASample
pom.xml に以下を書いておく。
pom.xml に追加する jar はここらへんを参考にしました。注意点としては、JPA を使用するには、hibernate-entitymanager というモジュールが必要なのでそれを必ず追加しておくこと。
あとは、永続化先に使用する DB の JDBC も忘れずに追加しておくこと(今回は H2 database を使用)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>JPASample2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JPASample2</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.8.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.8.Final</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.162</version>
</dependency>
</dependencies>
</project>
できたプロジェクトを Eclipse にインポートさせるため、以下のコマンドを叩いて Eclipse プロジェクトにしておく。
cd JPASample mvn eclipse:eclipse
できたら、Eclipse からインポートする。
データベース の準備
永続先として今回は H2 database を使用。Jar ファイル1つで完結するのでとても簡単。
ここから Jar を DL して Jar 実行。
java -jar h2-1.3.162.jar
Web コンソールが立ち上がると思うので、ログイン(ID、パスワードはそのままでOK)して、テーブルを作成しておく。
CREATE TABLE EMPLOYEE(ID INT PRIMARY KEY, NAME VARCHAR(255));
JPA の準備
まずテーブルにマッピングさせるエンティティクラスを作成する。
@Entity
public class Employee {
@Id
public int id;
public String name;
public Employee() {
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
}
次に、JPA が DB の設定などを行う persistence.xml を作成する。
配置場所はクラスパスが通っている場所に配置する(今回は src/main/resources/META-INF/persistence.xml に配置した)
Eclipse でちゃんとクラスパス設定を行うこと。
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="sample" transaction-type="RESOURCE_LOCAL">
<class>com.example.Employee</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="hibernate.connection.driver_class" value="org.h2.Driver" />
<property name="hibernate.connection.username" value="sa" />
<property name="hibernate.connection.password" value="" />
<property name="hibernate.connection.url" value="jdbc:h2:tcp://localhost:9092/demo" />
<property name="hibernate.max_fetch_depth" value="3" />
</properties>
</persistence-unit>
</persistence>
実行
あとは、JPA を使って実行するのみ。
public class App {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("sample");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
em.persist(new Employee(1, "近藤 勇"));
em.persist(new Employee(2, "土方 歳三"));
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
emf.close();
}
}
}
H2 database にデータがきちんと登録されていれば成功。
- Comments: 0
- Trackbacks: 0
Generics(Java)の型消去について
- 2011年12月4日 12:39 PM
- Java
Java の Generics の実装方式の型消去についてちょっと調べました。
そもそも ジェネリックプログラミングというのは、Java だけにあるものではなく色んな言語に同様の機能があるようです。
ジェネリックプログラミング wikipedia
Java の場合、言語仕様的に、どうやって Generics を実装しているかというと「型消去(type erasure)」によって行われている。
型消去とは、「コンパイル後のバイトコードに型情報を残さない」ということ。
具体的なサンプルコードで見てみる。
public void method15() {
List<String> list = new ArrayList<String>();
list.add("string");
String str = list.get(0);
}
public void method14() {
List list = new ArrayList();
list.add("string");
String str = (String) list.get(0);
}
これを、jad したものが以下。
public void method15();
0 new java.util.ArrayList [15]
3 dup
4 invokespecial java.util.ArrayList() [17]
7 astore_1 [list]
8 aload_1 [list]
9 ldc <String "string"> [18]
11 invokeinterface java.util.List.add(java.lang.Object) : boolean [20] [nargs: 2]
16 pop
17 aload_1 [list]
18 iconst_0
19 invokeinterface java.util.List.get(int) : java.lang.Object [26] [nargs: 2]
24 checkcast java.lang.String [30]
27 astore_2 [str]
28 return
public void method14();
0 new java.util.ArrayList [15]
3 dup
4 invokespecial java.util.ArrayList() [17]
7 astore_1 [list]
8 aload_1 [list]
9 ldc <String "string"> [18]
11 invokeinterface java.util.List.add(java.lang.Object) : boolean [20] [nargs: 2]
16 pop
17 aload_1 [list]
18 iconst_0
19 invokeinterface java.util.List.get(int) : java.lang.Object [26] [nargs: 2]
24 checkcast java.lang.String [30]
27 astore_2 [str]
28 return
確かに、コンパイルされると型情報が消去されて、Java 1.4 時代のものと同じになっている。
なぜ、Java では型消去という実装方式を採用したか?
C++ の場合、同様の機能にテンプレートというものがあるらしく、そちらはコンパイル後も型情報をインライン展開して残すらしいです。
で、Java の場合、なぜ型消去という実装方式を採用したかというと、「後方互換性」のためらしい。
先の例で見たように、コンパイル後のコードが全く同じになるので、1.4 以前で書かれたコードと 1.5 以上で書かれてコードがコードが混在していても実行できる、ということ。
なるほど、よく出来てる。
- Comments: 0
- Trackbacks: 1
Servlet 3.0 のアノテーションでは Filter の順番を指定できない?
- 2011年12月1日 9:20 PM
- Java
Servlet 3.0 になってから HttpServlet クラスの @WebServlet 同様に、Filter クラスも @WebFilter というアノテーションが指定できるようになっていて、やたら便利です。
@WebFilter(dispatcherTypes = { DispatcherType.REQUEST }, urlPatterns = { "/*" },
initParams = { @WebInitParam(name = "encoding", value = "UTF-8") })
public class EncodingFilter implements Filter {
private String encoding = null;
@Override
public void init(FilterConfig fConfig) throws ServletException {
encoding = fConfig.getInitParameter("encoding");
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding(encoding);
response.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
}
が、どうもアノテーションだけでは、Filter クラスの順番を定義することはできないようです。
なので、結局、web.xml に Filter を上から順番に書いていくしかないようです、残念。
(指定する方法があったら教えて欲しいです)
ちなみに @WebFilter に指定するアノテーション定義はこちら
http://docs.oracle.com/javaee/6/api/javax/servlet/annotation/WebFilter.html
やはり、順番を指定する指定は無いっぽい。
- Comments: 0
- Trackbacks: 0
LDRich という livedoor Reader のクライアントアプリを作りました
- 2011年11月22日 11:37 PM
- Android
Android で何か作りたくて、勢いで Galaxy S を買ってしまったので、アプリを作りました。
LDRich という Android アプリで、livedoor Reader のクライアントです。
ネーミングに特に意味はなくて、思いついたものは全て使われていて、ゴロで決めました。
Android 使っている人で、livedoor Reader ユーザーは使っていただけたら嬉しいです。
- Comments: 2
- Trackbacks: 0
msysGitのGit bashで日本語を使えるようになるまで
- 2011年8月8日 11:34 AM
- Git
最近、ようやくGitを使い始めました。サクサク感があっていいですね。あとGithubの存在が大きい。
で、表題なんですが、
僕はWindows派でmsysGit使ってるわけですが、デフォルト状態(Git Bash)ではコミットログの日本語が化けて出たりと何かと不便です。
そこで、いろいろ設定して日本語が表示できるようになろうという感じです。
lessとnkfをインストール
以下を参考に、lessとnkfを導入します。
http://sourceforge.jp/magazine/09/02/12/0530242/3
inputrc
set meta-flag on set input-meta on set output-meta on set convert-meta off set kanji-code utf-8
profile
export GIT_PAGER="nkf -s | less"
GIT_EDITORの設定は~/.gitconfigで行っているのでここでは設定しません。
~/.gitconfigの設定
以下のように修正。
[user] name = sinsengumi email = tomoya.sinsengumi@gmail.com [color] ui = auto [core] editor = 'C:/Program Files (x86)/sakura2-0-2-0/sakura.exe' -CODE=4 autocrlf = false [alias] ci = commit co = checkout st = status di = diff sh = show br = branch [i18n] commitencoding = UTF-8
editorの部分ではサクラエディタを起動してますが、「-CODE=4」はファイルを開く時にUTF-8で開くという起動オプションです。
ちなみにこの設定はGithubに上げてみました^^;
https://github.com/sinsengumi/Configs
参考
- Comments: 0
- Trackbacks: 0
- Search
- Feeds
- Meta





















