2012年12月28日金曜日

Jersey (JAX-RS) での JSON 取り扱いTIPS

JerseyはレスポンスとしてXMLを前提にしていて
JSON扱おうとするとたまに問題が起こるのでその対応をメモ


1.Listに要素がひとつしかないとArrayではなくObject渡してくる

このあたり参考にして、サーブレットを登録しているパッケージ中に
@Providerを定義する
http://tugdualgrall.blogspot.jp/2011/09/jax-rs-jersey-and-single-element-arrays.html

2.…to be continued


もしくは、stackoverflowにJSONパーサをJacksonに変更する方法が乗っていたので
これを利用すると全て解決しそう(うまく動かなかった)
how-can-i-customize-serialization-of-a-list-of-jaxb-objects-to-json


追記:
JSONの扱いは上記の方法を取る必要はなく、POJOMappingFeatureを利用すればいいもよう
Web.xmlのサーブレット定義に追加します

<init-param>
    <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
    <param-value>true</param-value>
</init-param> 

2012年12月23日日曜日

wine on mac でつまづいたこと

wineでアプリをインストールするにはwinetricksを利用するのですが
winetricksの挙動がおかしく、アプリ選択してインストールしようとしても
一向に先に進まない現象が起こっていました。

解決方法というか回避策としてコンソールからインストールしました。

1.winetricksで対象のアプリの一覧を取得する

winetricks apps list | grep -i "hoge"


2. 取得したアプリ名を指定してインストール

winetricks hoge


#手動でX window 入れた後にwine入れて競合してるのが問題なのだろうか


2012年12月9日日曜日

Java 重み付けランダム抽選

車輪の再発明かもしれませんが、
重み付けをしたランダム抽選ロジック作ったので共有


package org.chikishe.Util;

import java.util.ArrayList;
import java.util.Collection;

public class RandomUtil {
    @SuppressWarnings("unchecked")
    public static <T> T weightedRandom(Collection
<T> resource,
            Weight
<T> delegate) throws Exception {
        ArrayList
<WeightCompare> weightedResource = new ArrayList<WeightCompare> ();
        int totalWeight = 0;

        for (T temp : resource) {
            int weight = delegate.getWeight(temp);
            if (weight <= 0)
                continue;

            totalWeight += weight;
            weightedResource.add(new WeightCompare(temp, totalWeight));
        }

        if (totalWeight
<= 0)
            throw new Exception("No Resource");

        int random = randomNext(1, totalWeight + 1);

        int lower = -1;
        int upper = weightedResource.size();
        while (upper - lower
> 1) {
            int index = (lower + upper) / 2;
            if (weightedResource.get(index).weight
>= random) {
                upper = index;
            } else {
                lower = index;
            }
        }

        return (T) weightedResource.get(upper).obj;
    }

    public static int randomNext(int from, int to) {
        return (int) (Math.random() * (to - from) + from);
    }

    public interface Weight
<T> {
        int getWeight(T temp);
    }

    private static class WeightCompare {
        private Object obj;
        private int weight;

        public WeightCompare(Object obj, int weight) {
            this.obj = obj;
            this.weight = weight;
        }
    }
}


呼び出しは、次のようになります。

public String getTest4() {
        ArrayList<TestMember> members = new ArrayList<TestMember>();

        members.add(new TestMember(1, 50));
        members.add(new TestMember(2, 80));
        members.add(new TestMember(3, 130));

        try {

            TestMember member = RandomUtil.weightedRandom(members,
                    new Weight<TestMember>() {
                        public int getWeight(TestMember m) {
                            return m.weight;
                        }
                    });

            return "" + member.id;

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new WebApplicationException(500);
        }
    }

    private class TestMember {
        public int id;
        public int weight;

        public TestMember(int id, int weight) {
            this.id = id;
            this.weight = weight;
        }
    }

RandomUtil.weightedRandomはジェネリック型を受け取るようにしているので
第1引数のリストに指定した型を返します。
第2引数には重み付けを与えるメソッドの定義を渡しています。
サンプルの場合は、リストのTestMemberクラスのweightを重みとして取得しています。


ジェネリックの使い方がようやくわかってきた。


追記
2014/01/* Java 8でのラムダ式を使った使用方法はこちら

java でのmemcached ラッパー

Javaでmemcachedを利用する際に、

1.キーからオブジェクトget
2.オブジェクトがnullならデータ取得
3.2で取得したデータをmemcachedにset

というのは割りとよくある一連の実装だと思います。

前職でC#を使っているときはdelegate(ラムダ式)を使って
1つのメソッドで一連の処理を実装していました。


Javaでも同じように実装してみます。
Delegateはこんなかんじでいいのかな…
Delegate interfaceを定義、そのgetメソッドから取得するように。

public class Memcached {

    protected static MemCachedClient mcc = new MemCachedClient();

    static {
        SockIOPool pool = SockIOPool.getInstance();
        //設定
        pool.initialize();
    }
   
    public static <T> T get(String key, Memgetable<T> delegate, Date expire){
        Object ret = null;
        ret = mcc.get(key);
      
        if (ret != null) return ret;
      
        ret = (T) delegate.get();
      
        if (ret == null) return null;
      
        mcc.set(key, ret, expire);
      
        return ret;
    }  

    public interface Memgetable<T> {
        public T get();
    }

}



呼び出しは、


String key = "test_key";
final String value = "test_value";

Memcached.get(key, new Memgetable<String>() {
    public String get() {
        return value;
    }
}, new Date());



こんなかんじで、一行で出来ます。
Delegate interfaceにgetメソッドを定義し値がなければメソッドから値を取得します。


ラムダ式周りはC#が素晴らしすぎてJavaの非力さが露呈してしまう。。
Java 8ではもっと楽に実装できるようになるのかしら。

2012年12月8日土曜日

memcached for Windows 8 x64

Windows 8 x64 で memcachedをインストールする方法。


開発環境でmemcachedが必要だったのでインストールしようとしましたが、
ここにあるようなバイナリではサービスの追加に失敗して動きませんでした。

>memcached -d install

のコマンドで失敗します。(もしかして、管理者で実行しなかったからか…


 のでもう一つの方法、CouchbaseのMembaseをインストールして
memcached モードで利用します。

ダウンロードはここの下の方から


インストールする際に、Bucket Setting  画面で Bucket Type : Memcached を選択
すればインストールはOKです。


 では確認、

 >telnet localhost 11211 
 >stats
 
で反応があれば確認完了です。


 Memcached-Java-Clientでは今のところ問題なく動作しています。

2012年12月7日金曜日

JNDIのカスタムPropertyクラス

JNDIに設定を集約できないかと思い、カスタムPropertyクラス作ってみました。

 Jettyを利用している場合、
 xmlのNew要素に指定したclass属性のクラスをnewして、
その子要素のSetに指定した値をname属性と同じ名前のメンバに代入します。

何言ってるかわからんないですね。。つまり、
<New class="org.chikishe.Property.MemcachedProp">
                <Set name="servers">127.0.0.1:8091</Set>
                <Set name="weights" type="java.lang.String" >1</Set>


というxmlは、

MemcachedProp prop = new MemcachedProp();
prop.setServers("127.0.0.1:8091");
prop.setWeights("1"):
 …

というふうに解釈されます。

Memcached-Java-Client用のPropertyクラスを作るとするとこうなります。


MemcahedProp.java:

package org.chikishe.Property;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class MemcachedProp {
    private String[] servers;

    private Integer[] weights;

    private int initConn;

    private int minConn;

    private int maxConn;

    private int maxIdle;

    private int maintSleep;

    private boolean nagle;

    private int socketTO;

    private int socketConnectTO;

    public String[] getServers() {
        return servers;
    }

    public void setServers(String servers) {
        this.servers = servers.split(",");
    }

    public Integer[] getWeights() {
        return weights;
    }

    public void setWeights(String weights) {
        String[] strings = weights.split(",");
        Integer[] ints = new Integer[strings.length];
        for (int i = 0; i < strings.length; i++)
            ints[i] = Integer.parseInt(strings[i]);

        this.weights = ints;
    }

    public int getInitConn() {
        return initConn;
    }

    public void setInitConn(int initConn) {
        this.initConn = initConn;
    }

    public int getMinConn() {
        return minConn;
    }

    public void setMinConn(int minConn) {
        this.minConn = minConn;
    }

    public int getMaxConn() {
        return maxConn;
    }

    public void setMaxConn(int maxConn) {
        this.maxConn = maxConn;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public int getMaintSleep() {
        return maintSleep;
    }

    public void setMaintSleep(int maintSleep) {
        this.maintSleep = maintSleep;
    }

    public boolean isNagle() {
        return nagle;
    }

    public void setNagle(boolean nagle) {
        this.nagle = nagle;
    }

    public int getSocketTO() {
        return socketTO;
    }

    public void setSocketTO(int socketTO) {
        this.socketTO = socketTO;
    }

    public int getSocketConnectTO() {
        return socketConnectTO;
    }

    public void setSocketConnectTO(int socketConnectTO) {
        this.socketConnectTO = socketConnectTO;
    }

    public MemcachedProp() {
    }

    public static MemcachedProp getInstance() throws NamingException {

        InitialContext ic = new InitialContext();
        Context envCtx = (Context) ic.lookup("java:comp/env");
        MemcachedProp prop = (MemcachedProp) envCtx.lookup("memcached");
        return prop;
    }

}


jetty-env.xml:

<New id="memcached" class="org.eclipse.jetty.plus.jndi.Resource">
        <Arg></Arg>
        <Arg>memcached</Arg>
        <Arg>
            <New class="org.chikishe.Property.MemcachedProp">
                <Set name="servers">127.0.0.1:8091</Set>
                <Set name="weights" type="java.lang.String" >1</Set>
                <Set name="initConn">5</Set>
                <Set name="minConn">5</Set>
                <Set name="maxConn">250</Set>
                <Set name="maxIdle">21600000</Set>
                <Set name="maintSleep">30</Set>
                <Set name="nagle">false</Set>
                <Set name="socketTO">3000</Set>
                <Set name="socketConnectTO">0</Set>
            </New>
        </Arg>
    </New>


web-inf/web.xml :

 <resource-ref>
     <description>Memcached Property</description>
     <res-ref-name>memcached</res-ref-name>
     <res-type>org.chikishe.Property.MemcachedProp</res-type>
     <res-auth>Container</res-auth>
  </resource-ref>



呼び出しはこんなふうになります。

MemcachedProp prop = MemcachedProp.getInstance();

SockIOPool pool = SockIOPool.getInstance();

// set the servers and the weights
pool.setServers(prop.getServers());
pool.setWeights(prop.getWeights());



とりあえず、JNDIに設定をまとめるには使えそうです。
本来はstatic finalにしたいところ。
あと、 Memcached-Java-ClientのJNDI対応まだないんでしょうか?



2012年12月6日木曜日

Jersey の HttpServletRequest.getParameter仕様

Jerseyの開発でつまづいたので共有

JerseyでPost時のパラメータを取得するには、普段は@FormParamを利用します。
が、既存のライブラリを利用する際に、HttpServletRequest.getParameter, getParameterMap
を通して取得したい場合があります。

ただ、Jerseyの仕様でgetParameterはnullが返ってきます。
(内部のinputstreamがなくなっているからとか)

その場合には手段がいくつかあります。
1.filterを利用する
2.ServletContainerをextendsする


追記(2014/04/15): 
jetty 9あたリから@Injectを利用した方法があるようです要調査


上記を利用して@FormParam生成前に内部的にparameterを保持しておく
という案がありますが、資料なさ過ぎて私にはむりです。
 そこでとりあえず


3.@Consumesを使ってFormデータを直接受け取る

を利用して既存ライブラリのほうを修正しました。
Postは"application/x-www-form-urlencoded"のcontent-typeでデータを渡してくるので


 @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public void post(MultivaluedMap formParams) {

         … 

というふうにデータ取得ができます。
でこれをHttpServletRequestのgetPrameterに詰め込めればいいのですが
あきらめ。
既存ライブラリでgetParameterする際にデータを結合しました。

2012年1月15日日曜日

jetty + jersey + Amazon Product Advertising APIのサンプル

●xsdからjarファイルの作成

 AmazonのAmazon Product Advertising APIを利用する際には、
jaxbでxmlをパースするためにクラスの定義が必要です。
ここでは、アマゾンの提供しているxsdからクラス定義を含むjarファイルを作成します。

まず、アマゾンからAWSECommerceService.xsd をダウンロードし、
src/main/resourcesに設置。

pom.xmlを下記のように追加。
maven-compiler-pluginは、1.5以上に設定する。

<groupId>test</groupId>
<artifactId>aws-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>AWSECommerceService</name>

<dependencies>
<dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.2.6</version>
  </dependency> 
  </dependencies>
  <build>
      <plugins>
           <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>6</source>
                <target>6</target>
            </configuration>
      </plugin>
     <plugin>
          <groupId>org.jvnet.jaxb2.maven2</groupId>
          <artifactId>maven-jaxb22-plugin</artifactId>
          <version>0.8.0</version>
      </plugin> 
     </plugins>
  </build>

maven packageを実行すると、target以下にjarファイルが作成されます。
(著者は、target/generated-sources/xjc以下のjavaファイルをインポートして
利用しているのでjarが使用可能か未確認です。)


●signerの実装
2009年8月15日以降、Product Advertising APIは、リクエストに署名認証を含めなければならなくなりました。

下記にAmazonの提供している署名のサンプルがあります。
 Java Sample Code for Calculating Signature Version 2 Signatures


このサンプルのままでは、リクエストに改行が入ってしまって動かないので、
 76行目の
 Base64 encoder = new Base64();

  Base64 encoder = new Base64(0);
に変更する必要があります。

それと、日本での検索をしたい場合は、
 33行目 endpoint を "ecs.amazonaws.jp"に変更します。


 ●取得してみる
 作成したjar、signerをインポートしてjerseyを利用して検索結果をそのまま表示する
 サンプルを作ってみます。

src/main/java/gauuud/amazon/service/ItemSearchResource.java

package gauuud.amazon.service;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.amazon.associates.sample.SignedRequestsHelper;
import com.sun.jersey.api.client.Client;
import com.amazon.webservices.awsecommerceservice._2011_08_01.ItemSearchResponse;

@Path("/item_search")
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public class ItemSearchResource {
    @GET
    public ItemSearchResponse get(String word) throws Exception {

        SignedRequestsHelper signer = new SignedRequestsHelper();
       
        Map map = new HashMap();
        map.put("Service","AWSECommerceService");
        map.put("Operation","ItemSearch");
        map.put("Version","2011-08-01");
        map.put("SearchIndex","Books");
        map.put("Keywords","java");
       
        map.put("AssociateTag","[アソシエートID]");
       
        String url = signer.sign(map);
       
        Client c = Client.create();           

        ItemSearchResponse result = c.resource(url).get(ItemSearchResponse.class);

        return result;
    }
}



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

  <groupId>gauuud</groupId>
  <artifactId>aws-sample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>aws-sample</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
  <dependency>
   <groupId>org.codehaus.jettison</groupId>
   <artifactId>jettison</artifactId>
   <version>1.3</version>
  </dependency>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-core</artifactId>
   <version>1.11</version>
  </dependency>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-client</artifactId>
   <version>1.11</version>
  </dependency>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-server</artifactId>
   <version>1.11</version>
  </dependency>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-servlet</artifactId>
   <version>1.11</version>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>servlet-api</artifactId>
   <version>2.5</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-json</artifactId>
   <version>1.11</version>
  </dependency>
  </dependencies>
  <build>
   <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
       <version>8.0.0.v20110901</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>
          <webAppSourceDirectory>${basedir}/webapp</webAppSourceDirectory>
          <webXmlFile>${basedir}/webapp/WEB-INF/web.xml</webXmlFile>
          <contextPath>/</contextPath>
          <connectors>
            <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
              <port>8080</port>
              <maxIdleTime>60000</maxIdleTime>
            </connector> 
          </connectors>
          <systemProperties>
            <systemProperty>
              <name>org.apache.commons.logging.Log</name>
              <value>org.apache.commons.logging.impl.SimpleLog</value>
            </systemProperty>
          </systemProperties>
        </configuration>
      </plugin>      
      <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
   <configuration>
    <source>6</source>
    <target>6</target>
   </configuration>
      </plugin>
   </plugins>
  </build>
</project>
 
 
src/main/webapp/WEB-INF/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_3_0.xsd"
 version="3.0">
 <display-name>aws-sample</display-name>
     <welcome-file-list>
      <welcome-file>index.html</welcome-file>
     </welcome-file-list>

 <servlet>
  <servlet-name>jersey</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
   <param-name>com.sun.jersey.config.property.packages</param-name>
   <param-value>gauuud.amazon.service</param-value>
  </init-param>
  
        <load-on-startup>10</load-on-startup>
 </servlet>

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

maven jettry:run
を実行して
localhost:8080/rest/search_item
にアクセスするとxmlが取得できるはず。