mavenでandroidを管理してみた

androidmavenで管理したい!ってことで、実際にやってみた。

まずは、eclipseの設定から。 eclipseheliosまたは、indigoが入っている事が前提。

  • ADTのインストール。
  • m2eのインストール。(m2eclipseじゃないよ。)
  • m2e-androidのインストール。


んで、mavenarchetypegithub上で公開されているので、そちらを使用。
今回は、android-release-archetypeを使用した。

mvn archetype:generate \
  -DarchetypeArtifactId=android-release \
  -DarchetypeGroupId=de.akquinet.android.archetypes \
  -DarchetypeVersion=1.0.5 \
  -DgroupId=com.foo.bar \
  -DartifactId=my-android-project \
  -Dpackage=com.foo.bar.android \
  -Dplatform=4

あとは、eclipseにmavenImportするだけ。

ADTのバージョンが12以上になると、以下のプラグインではAPKBuilderのエラーが出てダメでした。

  • m2eclipse
  • m2eclipse-android-integration

SNSボタンを設置してみた

自分が使用した、SNSボタンの設置方法。

設置方法

facebook like button

http://developers.facebook.com/docs/reference/plugins/like/

上記のURLから自分のサイトのlikeボタンを作成した場合の例が下記。
iframeとxfbmlの2種類がある。個人的にはiframe版の方がよいかと思う。

<!-- iframe版 -->
<iframe src="http://www.facebook.com/plugins/like.php?app_id=204714862912458&amp;href=http%3A%2F%2Fsites.google.com%2Fsite%2Fdaimyou0410%2F&amp;send=false&amp;layout=box_count&amp;width=450&amp;show_faces=true&amp;action=like&amp;colorscheme=light&amp;font&amp;height=90" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:450px; height:90px;" allowTransparency="true"></iframe>

<!-- XFBML版 -->
<div id="fb-root"></div>
<script src="http://connect.facebook.net/ja_JS/all.js#xfbml=1"></script>
<fb:like href="http://sites.google.com/site/daimyou0410/" send="false" layout="box_count" width="450" show_faces="true" font=""></fb:like>
mixi check

http://developer.mixi.co.jp/connect/mixi_plugin/mixi_check/spec_mixi_check/

mixi developerに登録する。その後、サービス登録し、mixiチェックキーを発行してもらい、ページの内容、許可ドメイン等を書く。
下記が自分のサイトでmixiチェックを設置する場合の例。

<a href="http://mixi.jp/share.pl" data-button="button-1" class="mixi-check-button d_inline" data-key="発行したチェックキー" data-url="http://sites.google.com/site/daimyou0410/" target="_blank">mixiチェック</a>
<script type="text/javascript" src="http://static.mixi.jp/js/share.js"></script>
twitter tweet button

http://twitter.com/about/resources/tweetbutton

上記のURLから自分のサイトのtweetボタンを作成した場合の例が下記。

<a href="http://twitter.com/share" class="twitter-share-button" data-url="http://sites.google.com/site/daimyou0410/" data-text="#soba" data-count="vertical" data-via="daimyo_wakatono" data-lang="ja">Tweet</a>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>

snsのサーバがアクセス可能にする

下記のUser-Agentを見て、アクセス可能にする。
twitterの場合は、snsサーバ側からコンテンツを取得しにこないので、特に必要はない。

mixi

mixi-check/1.0 (http://mixi.jp/)

Open Graph タグの設置

そのページが何について書かれているかの情報を記述する。
こちらを参照。

<meta property="og:title" content="タイトル" />
<meta property="og:url" content="ページのURL" />
<meta property="og:site_name" content="サイト名" />
<meta property="og:description" content="詳細" />

facebook URLリンターを利用して、どのように解析されるかを確認することが出来る。

テストパッケージをjarに固めてみた

フレームワークを自作するときに、共通的なテストユーティリティを提供する場合があった。
その場合、pomの設定でmvn installするとテストパッケージ(src/test配下)をjarに固めることが出来る。

pomの設定(共通的なテストユーティリティ)

クラス、ソースとJavaDocをJarに固める設定。

...

<build>
<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>test-jar</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-javadoc-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>jar</goal>
                    <goal>test-jar</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-source-plugin</artifactId>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>jar</goal>
                    <goal>test-jar</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>
</build>

...

pomの設定(使用する側)

実際に提供されたテストユーティリティを使う側の設定。
dependencyに先ほど作成したJarの依存設定を追加する。

...

<dependencies>
    <dependency>
        <groupId>com.myco.app</groupId>
        <artifactId>hoge</artifactId>
        <classifier>tests</classifier>
        <scope>test</scope>
    </dependency>
</dependencies>

...

tips

mvn installでパッケージングするが、ビルド時にテストが走ってしまうので、テストをパスしたい場合のコマンド。

テスト対象オプションの「test」に存在しないテストを指定。
テストの失敗を無視する設定「maven.test.failure.ignore」をtrueにする。
※無理やり感は満載です。

$ mvn install -Dtest=none -Dmaven.test.failure.ignore=true

※ちなみに下記でもテストをパスできるが、test用のjarが出来ません。

$ mvn install -Dmaven.test.skip=true

S2JDBCで複数データソースを使い分ける

S2JDBCで複数データソースを使い分ける方法はこちらにあった。
今回自分がしたいのは、同じ構成のテーブルでデータソースを分けたいので、dataSourceFactoryを使って分けるようにする。
一応自分がした設定を記述してみる。

設定ファイル

jdbc_datasource1.diconとjdbc_datasource2.diconはこちらにあるような感じで書いた。
で、2つのファイルをインクルードして、SelectableDataSourceProxyとDataSourceFactoryImplを定義した。
s2jdbc.diconもjdbcManagerは1つにするので、変更していない。

jdbc_datasource1.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
    
    ....
    
<components namespace="jdbc">
    <component name="datasource1DataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>
jdbc_datasource2.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
    "http://www.seasar.org/dtd/components24.dtd">
    
    ....
    
<components namespace="jdbc">
    <component name="datasource2DataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>
jdbc.dicon
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components namespace="jdbc">
    <include path="jdbc_datasource1.dicon"/>
    <include path="jdbc_datasource2.dicon"/>
    
    <component name="dataSource" class="org.seasar.extension.datasource.impl.SelectableDataSourceProxy"/>
    <component name="dataSourceFactory" class="org.seasar.extension.datasource.impl.DataSourceFactoryImpl"/>
</components>

インターセプタ

データソース用のアノテーションenumとインターセプタを作った。
アノテーションで指定されているデータソースenumを見て、データソースを動的に切り替える仕組み。

インターセプタ
public class DataSourceSettingInterceptor extends AbstractInterceptor {
    /** データソースファクトリ */
    @Resource
    protected DataSourceFactory dataSourceFactory;

    /** デフォルトデータソース */
    private static final DataSourceType DEFAULT_DATA_SOURCE = DataSourceType.DATASOURCE1;

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        DataSourceType currentDataSourceType = findCurrentDataSourceType();
        try {
            DataSourceType dataSourceType = findDataSourceType(invocation.getMethod());
            dataSourceFactory.setSelectableDataSourceName(dataSourceType.getDataSourceName());

            return invocation.proceed();
        } finally {
            dataSourceFactory.setSelectableDataSourceName(currentDataSourceType.getDataSourceName());
        }
    }

    /**
     * 現在のデータソースタイプを取得する。<br>
     * @return 現在のデータソースタイプ
     */
    private DataSourceType findCurrentDataSourceType() {
        String currentDataSourceName = dataSourceFactory.getSelectableDataSourceName();

        if (StringUtils.isEmpty(currentDataSourceName)) {
            return DEFAULT_DATA_SOURCE;
        }

        DataSourceType dataSourceType;
        try {
            dataSourceType = DataSourceType.toDataSourceType(currentDataSourceName);
        } catch (IllegalArgumentException e) {
            // 想定外のデータソース名が渡された場合は、デフォルトデータソースを返す。
            return DEFAULT_DATA_SOURCE;
        }

        return dataSourceType;
    }

    /**
     * データソースタイプを取得する。<br>
     * @param method メソッド
     * @return データソースタイプ
     */
    private DataSourceType findDataSourceType(Method method) {
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource == null) {
            return DEFAULT_DATA_SOURCE;
        }

        return dataSource.type();
    }
}
アノテーション
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    /**
     * データソースタイプを設定する。<br>
     */
    DataSourceType type();

}
enum
public enum DataSourceType {
    /** データソース1用 */
    DATASOURCE1("datasource1"),

    /** データソース2用 */
    DATASOURCE2("datasource2");

    /** データソース名 */
    private String dataSourceName;

    /**
     * コンストラクタ<br>
     * @param newDataSourceName データソース名
     */
    private DataSourceType(String newDataSourceName) {
        this.dataSourceName = newDataSourceName;
    }

    /**
     * データソース名を取得する。<br>
     * @return データソース名
     */
    public String getDataSourceName() {
        return this.dataSourceName;
    }

    /**
     * 指定のデータソース名から対応するデータソースタイプを取得する。<br>
     * @param dataSourceName データソース名
     * @return 対応するデータソースタイプ
     */
    public static DataSourceType toDataSourceType(String dataSourceName) {
        for (DataSourceType dataSourceType : DataSourceType.values()) {
            if (dataSourceType.getDataSourceName().equals(dataSourceName)) {
                return dataSourceType;
            }
        }
        throw new IllegalArgumentException("指定のデータソース名に合致するデータソースタイプはありません。");
    }
}

インターセプタの使い方

一応、使い方。

@DataSource(type = DataSourceType.DATASOURCE1)
public String hoge() {
    // do something
}



あと、一部の機能のみデータソースを切り替えて実行したいときのために、エグゼキュータを作ってみた。

エグゼキュータ

public final class SelectableDataSourceExecutor {
    /**
     * コンストラクタ<br>
     */
    private SelectableDataSourceExecutor() {
    }

    /**
     * データソースを指定のデータソースタイプに切り替えて実行する。
     * @param <RESULT> 戻り値の型
     * @param dataSourceType データソースタイプ
     * @param handler ハンドラ
     * @return 結果
     */
    public static <RESULT> RESULT execute(DataSourceType dataSourceType, Handler<RESULT> handler) {
        DataSourceFactory dataSourceFactory = SingletonS2Container.getComponent(DataSourceFactory.class);

        String currentDataSourceName = dataSourceFactory.getSelectableDataSourceName();
        try {
            dataSourceFactory.setSelectableDataSourceName(dataSourceType.getDataSourceName());
            return handler.handle();
        } finally {
            dataSourceFactory.setSelectableDataSourceName(currentDataSourceName);
        }
    }

    /**
     * データソースの切り替え時に呼び出されるハンドラ。<br>
     * @param <RESULT> 結果の型
     */
    public interface Handler<RESULT> {
        /**
         * データソースの切り替え後に実行する。<br>
         * @return 結果
         */
        RESULT handle();
    }
}

エグゼキュータの使い方

一応、使い方。

public void hoge(final HogeDao dao, final HogeEntity entity)
    // do something
    
    SelectableDataSourceExecutor.execute(DataSourceType.DATASOURCE1, new SelectableDataSourceExecutor.Handler<Integer>() {
        /**
         * {@inheritDoc}
         */
        @Override
        public Integer handle() {
            return dao.insert(entity);
        }
    });
    
    // do something
}

NodeJSをインストールしてみた

前から気になっていたNodeJSの開発をするために、インストールしたので、まとめておく。
基本的には、UnixとかMacでのインストールになるので、Windowsの場合はCYGWINでのインストールとなる。

設定環境


CYGWINのインストール&設定

CYGWINダウンロードする。

node.jsをインストールするために、必要なパッケージを導入。
今回、自分がいれたのは、下記。


インストール後、まれにDLLが読み込めないので、cygwin\bin\ash.exeで下記のコマンドをうつ。
cygwin\bin配下を環境変数「PATH」に設定しておかないと自分の環境ではうまくいかず。

$ rebaseall

node.jsのインストール

CYGWINを起動する。

一応、パッケージがインストールされているかの確認。

$ gcc --version
$ make --version
$ python --version

今回は、wgetでダウンロードする。(GITでもいい。)

$ wget http://nodejs.org/dist/node-v0.4.8.tar.gz
$ tar xvf node-v0.4.8.tar.gz

ダウンロード後、node.jsのインストールガイドにしたがって、コマンドをうつ。

$ ./configure
$ make
$ make install

※makeに時間が結構かかる。

インストールの確認。

$ node --version

動作検証

インストールが完了したら、動作検証を行う。
とりあえず、お決まりの「Hello world.」を表示するプログラム。

sample.js
var http = require('http');

http.createServer(function (request, response) {
	response.writeHead(200, {'Content-Type': 'text/plain'});
	response.end('Hello World\n');
}).listen(8124);

で、起動。

$ node sample.js

http://localhost:8124/で起動確認できる。
ブラウザ上に「Hello world」と表示された。

npmのインストール

$ curl http://npmjs.org/install.sh | sh

で、npmで今回使うsocket.ioのインストール。

$ npm install socket.io


出来たー。

Ehcacheとterracottaの連携を試してみた。

大量のアクセスがあるWebサービスを運用している場合に、DBの負荷が非常に高くなっている場合がある。
RDBMSはスケールアウトしずらく、スケールアップでその高負荷に対応するなどして、ボトルネックを解消しようとすると思う。

RDBMSの高負荷の解消のためには、キャッシュ機構を使用して、DBへのアクセスを減らすという方法がある。
キャッシュ機構には、自分の知っているものは下記がある。

キャッシュ機構


今回は、この中でEhcacheを使用したいと思う。

Ehchace

オープンソースの分散キャッシュサーバ。
memcachedと比べると、多機能である。

●キャッシュ方法

ディスクキャッシュ、メモリキャッシュをサポートする。
(※memcachedはメモリキャッシュのみ)

●キャッシュの追い出しポリシー
  • LRU(Last Recently Used)

  最後に参照された日時が最も古い順に削除する。

  • LFU(Last Frequently Used)

  参照された回数が最も少ない要素から順に削除する。

  • FIFO(First In First Out)

  要素がキャッシュに追加された順に削除する。
memcachedLRUが採用されている。

レプリケーション

RMI(Remote Method Invocation)や、JMS(Java Message Service)を使用してレプリケーション・キャッシュ*1を実現することができる。
また、後で記述するが、Terracottaを用いたレプリケーションも可能である。

●その他機能

トランザクション、ロック、モニタリング等の機能も存在する。

Terracotta

複数のJVM間でJavaオブジェクトのクラスタリングを行う。
また、HiberfnateやSpringとの連携も容易である。(各ミドルウェア向けのモジュールが提供されている)

Ehcacheのインストール

現在、最新のバージョンは2.4.2。
今回はmaven2を使用してプロダクトをインストールする。
pom.xmlに下記を追記する。

<repository>
    <id>terracotta-release</id>
    <url>http://repo.terracotta.org/maven2</url>
    <releases><enabled>true</enabled></releases>
    <snapshots><enabled>false</enabled></snapshots>
</repository>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.4.2</version>
    <type>pom</type>
</dependency>

また、APIでslf4jを使用しているので、下記も追記する。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-jdk14</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.6.1</version>
</dependency>

Terracottaのインストール

terracottaサーバの起動のために、Terracottaサイトから下記をダウンロードする。
現在の最新バージョンは3.5.1。
terracotta-3.5.1-installer.jar

下記コマンドでインストールする。

java -jar terracotta-3.5.1-installer.jar

また、TerracottaのruntimeAPIをダウンロードするために、pom.xmlに下記を記述する。

<dependency>
    <groupId>org.terracotta</groupId>
    <artifactId>terracotta-toolkit-1.2-runtime-ee</artifactId>
    <version>3.1.0</version>
</dependency>

EhcacheとTerracottaの連携

下記のような構成の場合のEhcacheとTerracottaの連携方法をサンプルとして記述します。

●システム構成

アプリケーションサーバが3台、Terracottaサーバが2台でミラーリング構成になっている。
DBサーバは1台。

●設定ファイルの作成

EhcacheとTerracottaを連携するためには、ehcache.xmlを作成する必要がある。
ehcache.xmlはクラスパスのルート位置に作成する。

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect">

    <!-- ディスクキャッシュ時のデータ保存領域 -->             
    <diskStore path="java.io.tmpdir"/>

    <!-- terracottaの設定 -->
    <!--
    <terracottaConfig url="localhost\:9510, your.domain:9510" />
    -->
    <terracottaConfig>
	   <tc-config>
	       <system>
	           <configuration-model>development</configuration-model>
	       </system>
	       <servers>
	           <server host="localhost" name="server1">
                    <!-- permanent-store(ディスクに保存)に設定しないと再接続ができない。 -->
                    <dso>
                        <persistence>
                        <mode>permanent-store</mode>
                        </persistence>
                    </dso>
               </server>
               <server host="your.domain" name="server2">
                    <dso>
                        <persistence>
                        <mode>permanent-store</mode>
                        </persistence>
                    </dso>
               </server>
           </servers>
           <clients>
               <logs>C:\app\logs-%i</logs>
           </clients>
	   </tc-config>
    </terracottaConfig>
    
    <!-- デフォルトキャッシュ -->
    <defaultCache />

    <!--
        name: キャッシュ名
        eternal:キャッシュオブジェクトの有効期限の有無(true:無)
        maxElementsInMemory:メモリ上にキャッシュするオブジェクトの最大数(0の場合は無制限)
        maxElementsOnDisk:ディクス上にキャッシュするオブジェクトの最大数(0の場合は無制限)
        overflowToDisk:キャッシュオブジェクトの最大数を超えた時、Diskに保持するかどうか。(true:する) ※terracottaを使用する場合はfalseに設定する。
        timeToIdleSeconds:未アクセスの削除期限
        timeToLiveSeconds:キャッシュオブジェクトの寿命
        memoryStoreEvictionPolicy:メモリに格納するエントリが最大値に達したときの振る舞い(LRU、FIFOまたは、LFU)
    -->
	<cache
    	    name="terracottaTest"
            eternal="false"
            maxElementsInMemory="1000"
	    maxElementsOnDisk="10000"
            overflowToDisk="false"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="3600"
            memoryStoreEvictionPolicy="LFU">
            <!-- terracottaを使用する -->
	    <terracotta
                clustered="true"
        	valueMode="serialization"
        	consistency="strong"
        	storageStrategy="DCV2">
            <!-- 読み取り中にタイムアウトした場合は、ローカルから読み取る設定 -->
            <nonstop
                enabled="true"
                immediateTimeout="false"
                timeoutMillis="1000">
                <timeoutBehavior type="localReads" />
            </nonstop>
        </terracotta>
    </cache>
</ehcache>

次に、Terracottaの設定ファイル「tc-config.xml」を任意の位置に作成する。
serversタグで書かれたサーバの起動順によって、アクティブサーバとスタンバイサーバになる。

tc-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-6.xsd">
	<system>
       <configuration-model>development</configuration-model>
	</system>
    <servers>
        <server host="localhost" name="server1">
            <data>%(user.home)/terracotta/server-data</data>
            <logs>%(user.home)/terracotta/server-logs</logs>
        </server>
        <server host="your.domain" name="server2">
            <data>%(user.home)/terracotta/server-data</data>
            <logs>%(user.home)/terracotta/server-logs</logs>
        </server>
        <ha>
            <mode>networked-active-passive</mode>
            <networked-active-passive>
                <election-time>5</election-time>
            </networked-active-passive>
        </ha>
    </servers>
</tc:tc-config>
Terracottaサーバの起動

Windowsの場合とMac/Linuxの場合で起動バッチファイルまたはシェルファイルとなる。
先ほど作成した設定ファイルを使用する場合は、-fオプションで設定ファイルのパスを指定するとよい。

{terracottaのインストールディレクトリ}\bin\start-tc-server.bat (-f tc-config.xml)
{terracottaのインストールディレクトリ}/bin/start-tc-server.sh (-f tc-config.xml)
●アプリの設定

今回はWEBアプリのフレームワークSeasar2を使用する。
app.diconにキャッシュインスタンスを作成する。

app.dicon
<component name="cacheManager" class="net.sf.ehcache.CacheManager">
    @net.sf.ehcache.CacheManager@getInstance()
    <destroyMethod name="shutdown" />
</component>

実際にキャッシュに保存、取得するロジックは下記。

public class EhcacheLogic {

    @Resource
    protected CacheManager cacheManager;

    private Cache cache;

    /**
     * 初期処理。
     */
    public void init() {
        if (!this.cacheManager.cacheExists("terracottaTest")) {
            this.cacheManager.addCache("terracottaTest");
        }
        cache = cacheManager.getCache("terracottaTest");
    }

    /**
     * キャッシュに保存する。
     * @param key キー値
     * @param value
     */
    public void put(String key, String value) {
        Element element = new Element(key, value);
        cache.put(element);
    }

    /**
     * キャッシュから取りだす。
     * @param key キー値
     * @return
     */
    public Object get(String key) {
        Element element = cache.get(key);
        if (element == null) {
            return new String("");
        }
        Serializable originalResult = element.getValue();

        return SerializationUtils.clone(originalResult);
    }

    /**
     * キャッシュから削除する。
     * @param key キー値
     */
    public void remove(String key) {
        cache.remove(key);
    }
}

Terracottaでのクラスタリングは検証中であるが、からり早く各JVMにオブジェクトがコピーされていた。
Terracottaには開発者コンソールというものもある。どのような設定で動作しているのか、また、キャッシュの保存、取得、取得ミスなどの情報が閲覧できる。
調査をすすめて、こちらに追記していきたいと思う。

EhcacheとTerracottaの連携に関して、下記の本を参考にさせていただいた。

WEB+DB PRESS Vol.61

WEB+DB PRESS Vol.61

*1:あるアプリケーションサーバのキャッシュにデータを格納すると他のアプリケーションサーバのキャッシュへデータをレプリケートするキャッシング方式