<

![endif]-->

fc2ブログ

Javaでgrizzlyを使って簡単WebServer

WebアプリでExcelを作るのはJavaのpoiが一番安定していて使いやすいと思っています。
そこでバックエンドでExcelを作るためのサーバを作ることにしました。
tomcatとか準備するのは面倒ですし、ゼロからサーバを書くのも大変なのでgrizzlyでサーバを書きました。
package jp.uniquevision.server;

import java.io.File;

import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;

public class App
{
private static final String STOP_PATH = "/tmp/server_stop";

public static void main( String[] args )
{
HttpServer server = HttpServer.createSimpleServer();
server.getServerConfiguration().addHttpHandler(new HttpHandler() {
public void service(Request request, Response response) throws Exception {
final String data = "alive";
response.setContentType("text/plain");
response.setContentLength(data.length());
response.getWriter().write(data);
}}, "/alive");
try {
server.start();
File file = new File(STOP_PATH);
for (;;) {
Thread.sleep(1000);
if (file.exists()) {
break;
}
}
} catch (Exception e) {
System.err.println(e);
}
}
}

起動させてブラウザでhttp://localhost:8080/aliveにアクセスすると「alive」を返します。
後はやりたい処理のHttpHandlerを書いて、パスと結びつければOKです。Excel出すなりPDF出すなりなんでもOK。

grizzlyのサンプルではSystem.in.readlnで終了処理を行っているのですが、これだとバックグラウンドで動かすと標準入力がEOFになってしまい機能しません。
そこで、停止ファイルを見て処理を終了させるようにしました。
サーバ機能自体は別スレッドで動いているので、sleepしても止まりません。

Apache CXFでRESTプログラミング

RESTのWebサービスを作ろうと思い、Apache CXFを動かしてみました。
CXFをダウンロードして、samplesのjax_rs/basicをサンプルにある独自のサーバでは無く、warの構造にしてjettyで実行してみます。
まずは、pom.xml
<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>jp.uniquevision</groupId>
<artifactId>web</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>engine Maven Webapp</name>
<url>http://maven.apache.org</url>

<properties>
<jetty>7.1.0.v20100505</jetty>
<cxf>2.2.8</cxf>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-bundle-jaxrs</artifactId>
<version>${cxf.version}</version>
</dependency>
</dependencies>

<build>
<finalName>web</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

次はweb.xmlです。
<?xml version="1.0" encoding="UTF-8"?>
<web-app metadata-complete="true" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>Archetype Created Web Application</display-name>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/deploy-context.xml</param-value>
</context-param>

<servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

ここで注意点はweb-appにmetadata-complete="true"を追加することです。
まだ、よく分かってないんですが、これを付けずに実行するとExceptionが出て、ぐぐってみるとこれがないとJettyでversion=2.5で発生するそうです。
次に作成したクラスを定義するXML、deploy-context.xmlです。これはweb.xmlと同じところにおきます。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd"

default-lazy-init="false">


<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

<jaxrs:server id="myService" address="/">
<jaxrs:serviceBeans>
<ref bean="customerService" />
</jaxrs:serviceBeans>
<jaxrs:extensionMappings>
<entry key="xml" value="application/xml" />
</jaxrs:extensionMappings>
</jaxrs:server>

<bean id="customerService" class="jp.uniquevision.CustomerService" />

</beans>

あとは、jax_rs/basicの中にあるサーバ側のソースを指定のpackageにおいてコンパイルすればOKです。
で、mvn jetty:runで実行してhttp://localhost:8080/customerservice/customers/123にアクセスすれば
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Customer><id>123</id><name>John</name></Customer>

が返って来ます。
JavaのソースはほとんどPOJOなんですが、アノテーションでアクセスするパスを記述すると簡単にURLにマッピングできます。
また、戻り値のXMLはJavaのBeanクラスをマッピングしたものを自動で生成してくれます。
RESTプログラミングするには、CXFがかなり便利です。

Butterfly PersistenceをGuiceで使う

ORマッピングはコンピューターサイエンスのベトナム戦争なんて言われてたりして、私個人も自分でSQLを書かないと気持ち悪いので、JDBCでやるのが好きです。
しかしJDBCもパラメーターの設定とか、select結果の取得がめんどくさいです。
私がほしいのは、SQLが実行できて、パラメーターが与えやすくて、戻りがMapのListで返ってくる程度のもんですので、自作してもいいんですが、Butterfly Persistenceというライブラリがそういう機能を実装しているので、これを利用することにしました。
普通に使う方法はサイトを見てもらえばよいと思いますので、ここではGuiceを使って呼び出してみます。

GuiceとはGoogleの提供しているDIコンテナです。XMLで依存関係を定義するDIコンテナが多い中、これはXMLを使わずにJavaで定義します。Javaなので単純なスペルミスとか検出してくれるので便利です。
まずは、Guiceの定義クラスです。

import com.google.inject.Binder;
import com.google.inject.Module;
import com.jenkov.db.PersistenceManager;
public class MyModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(PersistenceManager.class).toProvider(PersistenceManagerProvider.class);
}
}

このソースはPersistenceManagerを要求しているところには、PersistenceManagerProviderから提供すると定義しています。
次にPersistenceManagerProviderです。

import javax.sql.DataSource;
import com.google.inject.Provider;
import com.jenkov.db.PersistenceManager;
import com.jenkov.db.jdbc.SimpleDataSource;
import com.jenkov.db.scope.ScopingDataSource;
public class PersistenceManagerProvider implements Provider {
private static final DataSource dataSource = new SimpleDataSource(
"org.postgresql.Driver",
"jdbc:postgresql://localhost:5432/test", "user", "password");

private static final ScopingDataSource scopingDataSource =
new ScopingDataSource(dataSource);

private static final PersistenceManager persistenceManager =
new PersistenceManager(scopingDataSource);

@Override
public PersistenceManager get() {
return persistenceManager;
}
}

PersistenceManagerが要求された時にgetメソッドが呼ばれて渡されます。
次に実際にPersistenceManagerを利用しているクラスです。

import java.util.List;
import com.google.inject.Inject;
import com.jenkov.db.PersistenceManager;
import com.jenkov.db.itf.IDaos;
import com.jenkov.db.itf.PersistenceException;

public class UserService {
@Inject
private PersistenceManager mgr;

public List getList() throws PersistenceException {
IDaos daos = mgr.createDaos();
List list = daos.getMapDao().readMapList("select * from t_user");
daos.closeConnection();
return list;
}

public void insUser(int id, String name) {
try {
mgr.getScopingDataSource().beginTransactionScope();
IDaos daos = mgr.createDaos();
daos.getJdbcDao().update("insert into t_user(id, name) values (?, ?)", id, name);
mgr.getScopingDataSource().endTransactionScope();
} catch (Exception e) {
mgr.getScopingDataSource().abortTransactionScope(e);
}
}
}

@Injectのアノテーションのある変数にGuiceによってPersistenceManagerが注入されます。
また上のコードではselectを実行してMapのListを取得することや、insert時のパラメーターの与え方が簡単にできることがわかると思います。
最後にmainです。

import java.util.List;
import com.google.inject.Guice;
import com.google.inject.Injector;

public class App
{
public static void main( String[] args ) throws Exception
{
Injector injector = Guice.createInjector(new UspEngineModule());
UserService userService = injector.getInstance(UserService.class);
userService.insUser(3, "ccc");
List list = userService.getList();
System.out.println(list);
}
}

実行結果は以下の通り。

[{id=1, name=aaa}, {id=2, name=bbb}, {id=3, name=ccc}]

Apache ClickとJettyをmavenでセットアップ

Apache ClickとJettyをmavenで構築する方法のメモです。
まず、プロジェクトの作成。

mvn archetype:create -DgroupId=jp.uniquevision.web -DartifactId=web -DarchetypeArtifactId=maven-archetype-webapp

できたフォルダへ移動して、生成されているpom.xmlを書き換えます。

<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>jp.uniquevision.web</groupId>
<artifactId>web</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>web Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<click2.2.0-RC1</click
<jetty7.1.0.v20100505</jetty
</properties>
<dependencies>
<dependency>
<groupId>org.apache.click</groupId>
<artifactId>click</artifactId>
<version>${click.version}</version>
</dependency>
<dependency>
<groupId>org.apache.click</groupId>
<artifactId>click-extras</artifactId>
<version>${click.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>web</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
</project>

Apache ClickのサイトのQuick Start Guideを見て、ページとソースを作成。
Webを実行する。

mvn jetty:run

Eclipseで管理する場合は以下のコマンドを実行

mvn eclipse:eclipse

wicketをjetty7で動かす

wicketをjetty7で動かすためのメモ。
eclipseで修正しながら、動作を確認できる方法です。
本当はeclipseのpluginでできればうれしいんだけど、まだjetty7には対応していないみたいです。

1. WicketのQuickStartで雛形となるプロジェクトの作成コマンドを生成

mvn archetype:create \r
-DarchetypeGroupId=org.apache.wicket \r
-DarchetypeArtifactId=wicket-archetype-quickstart \r
-DarchetypeVersion=1.4.3 \r
-DgroupId=com.mycompany \r
-DartifactId=myproject

2. コマンドを実行してプロジェクトを作成

3. pom.xmlの修正
3.1. log4jをlogbackに変更(そのままでもOK)

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>



<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.17</version>
</dependency>

3.2. 3つあるjetty6のdependencyを削除して、jetty7のdependencyを追加

<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-management</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>



<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>

3.3. pluginのartifactIdをmaven-compiler-pluginからjetty-maven-pluginに変更。
scanIntervalSecondsとcontextPathを追加

<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
</plugin>



<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/myproject</contextPath>
</webAppConfig>
</configuration>
</plugin>

3.4. wtpversionを追加

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
</configuration>
</plugin>



<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>

3.5. propertyの修正

<properties>
<wicket.version>1.4.3</wicket.version>
<jetty.version>6.1.4</jetty.version>
</properties>



<properties>
<wicket.version>1.4.3</wicket.version>
<jetty.version>7.0.0.v20091005</jetty.version>
</properties>

変換後のpom.xmlは以下のようになります。

<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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>myproject</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<!-- TODO project name -->
<name>quickstart</name>
<description></description>
<!--
TODO <organization> <name>company name</name> <url>company url</url>
</organization>
-->

<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<!-- WICKET DEPENDENCIES -->
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket</artifactId>
<version>${wicket.version}</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>0.9.17</version>
</dependency>

<!-- JUNIT DEPENDENCY FOR TESTING -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
<scope>test</scope>
</dependency>

<!-- JETTY DEPENDENCIES FOR TESTING -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<filtering>false</filtering>
<directory>src/main/resources</directory>
</resource>
<resource>
<filtering>false</filtering>
<directory>src/main/java</directory>
<includes>
<include>**</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<testResources>
<testResource>
<filtering>false</filtering>
<directory>src/test/java</directory>
<includes>
<include>**</include>
</includes>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</testResource>
</testResources>
<plugins>
<plugin>
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<optimize>true</optimize>
<debug>true</debug>
</configuration>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/myproject</contextPath>
</webAppConfig>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<downloadSources>true</downloadSources>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<wicket.version>1.4.3</wicket.version>
<jetty.version>7.0.0.v20091005</jetty.version>
</properties>
</project>


4. eclipse用のプロジェクトに変換
cd myproject
mvn eclipse:eclipse

5. eclipseにmaven用のjarのパスを追加
ワークスペースのパスは各自の環境に合わせて修正してください。
mvn -Declipse.workspace=C:Usersuserworkspace eclipse:add-maven-repo

6. eclipseを起動して、新規プロジェクトとして取り込む
testソースがjetty6用に記述されているのでtestフォルダごと削除する

7. jettyを起動
mvn jetty:run

8. ブラウザでhttp://localhost:8080/myprojectにアクセス

9. eclipse上のプログラムを修正する

10. 10秒経過してから、ブラウザをリロードし、修正を確認する

11. warを作る
mvn package