2011년 5월 24일 화요일

WebService에서 @XmlJavaTypeAdapter을 이용한 Java Map Type의 marshal, unmarshal

WebService에서 @XmlJavaTypeAdapter을 이용한 Java Map Type의 marshal, unmarshal

개요
WebService에서는 Key, Value형태로 Data을 가지는 complexType을 지원하지 않는다. 이는 WebService API을 개발하는데 있어 많은 제약이 된다. 이를 피하기 위해서 가장 좋은 방법은 Map Type을 사용하지 않고 List Type을 사용하는 방법이 있고, 또 본 글에서 논의할 JAXB(Java Architecture for XMBinding) 2.0에서 추가된 “@XmlJavaTypeAdapter” Annotation을 사용하는 방법이 있다.
사전 설치 내용
본 글에서 설명되는 Sample은 다음과 같은 환경에서 작성 되었습니다.
  • JWSDP 2.0
  • JBoss 4.0.3SP1
  • JDK 5.0
Sample One 일반적인 ComplexType을 사용하는 WebService
Addnumber 클래스는 다음 3가지의 WebService API 가지고 있다.
1. 파라미터로 받은 2개의 Int값을 더하여 ComplexType타입에 포함하여 Return한다.
2. 파라미터로 받은 2개의 Int값을 더하여 ComplexType에 포함시키고 List Type으로 Return한다.
3. ComplexType타입으로 받은 값을 더하여 ComplexType타입으로 Return한다.
package bam.ws.api;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.jws.WebService;
@WebService
public class Addnumber {
public ReturnSVOS addNumberVO(int number1, int number2){
ReturnSVOS vo = new ReturnSVOS();
vo.setName("addNumberVO");
vo.setNow(new Date());
vo.setRtnInt(number1+number2);
vo.setRtnDouble(number1+number2);
vo.setRtnLog(number1+number2);
return vo;
}
public List<ReturnSVOS> addNumberList(int number1, int number2){
ReturnSVOS vo = new ReturnSVOS();
vo.setName("addNumberVO");
vo.setNow(new Date());
vo.setRtnInt(number1+number2);
vo.setRtnDouble(number1+number2);
vo.setRtnLog(number1+number2);
List<ReturnSVOS> list = new ArrayList<ReturnSVOS>();
list.add(vo);
return list;
}
public ReturnSVOS addNumberParameterVO(ParameterVO param){
ReturnSVOS vo = new ReturnSVOS();
vo.setName("addNumberVO");
vo.setNow(new Date());
vo.setRtnInt(param.getNumber1()+param.getNumber2());
vo.setRtnDouble(param.getNumber1()+param.getNumber2());
vo.setRtnLog(param.getNumber1()+param.getNumber2());
return vo;
}
}

ComplexType ParameterVO
package bam.ws.api;
import java.io.Serializable;
public class ParameterVO implements Serializable {
private static finalong serialVersionUID = 1L;
private int number1;
private int number2;
public int getNumber1() {
return number1;
}
public void setNumber1(int number1) {
this.number1 = number1;
}
public int getNumber2() {
return number2;
}
public void setNumber2(int number2) {
this.number2 = number2;
}
}

ComplexType ReturnSVOS
package bam.ws.api;
import java.io.Serializable;
import java.util.Date;
public class ReturnSVOS implements Serializable {
private static finalong serialVersionUID = 1L;
private int rtnInt;
private String name;
private Date now;
private long rtnLog;
private double rtnDouble;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getNow() {
return now;
}
public void setNow(Date now) {
this.now = now;
}
public double getRtnDouble() {
return rtnDouble;
}
public void setRtnDouble(double rtnDouble) {
this.rtnDouble = rtnDouble;
}
public int getRtnInt() {
return rtnInt;
}
public void setRtnInt(int rtnInt) {
this.rtnInt = rtnInt;
}
public long getRtnLog() {
return rtnLog;
}
public void setRtnLog(long rtnLog) {
this.rtnLog = rtnLog;
}
}

Sample Two Map Type이 포함 된 ComplexType을 사용하는 WebService
getPerson() Webservice API는 Child 객체를 Map Type으로 포함하는 parentVO을 Return한다.
package bam.adapter;
import java.util.HashMap;
import javax.jws.WebService;
@WebService
public class MapSender {
public parentVO getPerson() {
parentVO vo = new parentVO();
child cd = new child();
cd.setAge(16);
cd.setName("Hurukku");
HashMap<String, child> map = new HashMap<String, child>();
map.put(cd.getName(), cd);
vo.setChilds(map);
return vo;
}
}

ComplexType child
package bam.adapter;
import java.io.Serializable;
public class child implements Serializable {
private static finalong serialVersionUID = 1L;
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

ComplexType parentVO
@XmlJavaTypeAdapter Annotation을 사용하게 되면 JAXB에서 Map Type을 마샬링, 언마샤링 할 때 Annotation에서 지정해준 Adapter에 전달해주게 된다. 여기서는 validateAdapter.class 실제 직렬화 작업은 Adapter 클래스에 명시해줘야 한다.
package bam.adapter;
import java.io.Serializable;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
public class parentVO implements Serializable {
private static finalong serialVersionUID = 1L;
@XmlJavaTypeAdapter(validateAdapter.class)
private Map<String, child> cds;
public Map<String, child> getChilds() {
return cds;
}
public void setChilds(Map<String, child> inChilds) {
this.cds = inChilds;
}
}


Adapter validateAdapter
이 클래스는 JAXB에게서 위임받은 직렬화 작업을 수행하게 된다. 개발자는 직렬화 코드를 직접 작성해야한다. 아래 코드를 보면 알 수 있겠지만 Map Type을 직렬화 한다는 것은 Map 자체를 직렬화하는게 아니라 안에 key는 무시되고 Value을 직렬화하는 것이다. 이 때문에 몇 가지 문제점들이 존재하게 된다.
1. marsha작업에서는 Map의 Value을 배열로 리턴 해주면서 Key는 제외된다.
2. unmarsha작업에서는 Value의 배열을 Map Type으로 만들면서 새로운 key값을 value에서 구해와야 한다.
3. 이 때문에 서버에서 보낸 Key와 클라이언트의 Key는 서로 다를 것이다.
package bam.adapter;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class validateAdapter extends XmlAdapter<child[], Map<String, child>> {
public Map<String, child> unmarshal(child[] value) {
Map<String, child> r = new HashMap<String, child>();
for (child c : value)
r.put(c.getName(), c);
return r;
}
public child[] marshal(Map<String, child> value) {
return value.values().toArray(new child[value.size()]);
}
}

Sample WebService 배포
1. 환경변수 추가
JWSDP 2.0이 설치되어 있다면 다음 환경변수를 System에 추가해준다. 여기서 “E:/CommonLib/jwsdp-2.0” JWSDP 설치 디렉토리이다.
set path=%path%;E:/CommonLib/jwsdp-2.0/jaxws/bin;
set classpath=%classpath%;E:/CommonLib/jwsdp-2.0/jaxws/lib/jaxws-api.jar;E:/CommonLib/jwsdp-2.0/jaxws/lib/jaxws-rt.jar;E:/CommonLib/jwsdp-2.0/jaxws/lib/jaxws-tools.jar;E:/CommonLib/jwsdp-2.0/jaxws/lib/jsr181-api.jar;E:/CommonLib/jwsdp-2.0/jaxws/lib/jsr250-api.jar;E:/CommonLib/jwsdp-2.0/jaxb/lib/jaxb-api.jar;E:/CommonLib/jwsdp-2.0/jaxb/lib/jaxb-impl.jar;E:/CommonLib/jwsdp-2.0/jaxb/lib/jaxb-xjc.jar;E:/CommonLib/jwsdp-2.0/jaxb/lib/jaxb1-impl.jar;

2. JBOSS에 JWSDP 2.0 Library Install
  • JWSDP의 “jaxb”, ”jaxws”, ”saaj”, ”sjsxp” 밑 lib 밑의 jar 파일을JBOSS_HOME/server/default/deploy 밑으로 복사한다. CalssPath에 추가된다면 어느 디렉토리든지 상관 없다.
  • “jwsdp-shared”/lib/resolver.jar 파일을 위와 동일한 위치에 복사한다.
  • JBOSS_HOME/server/default/lib/jboss-saaj.jar 파일을 삭제한다.

3. Compile
Sample One, Sample Two의 해당 소스 위치에서 Compile한다.
javac -d . bamwsapi*.java
javac -d . bamadapter*.java
4. WSD파일 자동 생성
Compile한 위치에서 다음을 실행한다.
wsgen -cp . -keep -d . -wsdbam.ws.api.Addnumber
wsgen -cp . -keep -d . -wsdbam.adapter.MapSender
5. web.xml, sun-jaxws.xm 파일을 생성한다.
web.xml
<?xmversion="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
<display-name>equusSpikeStepOne</display-name>
<description>equusSpikeStepOne</description>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>equusws</servlet-name>
<display-name>equusws</display-name>
<description>JAX-WS endpoint - Addnumber, MapSender</description>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>equusws</servlet-name>
<url-pattern>/addnumber</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>equusws</servlet-name>
<url-pattern>/mapsender</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
</web-app>


sun-jaxws.xml
<?xmversion="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>
<endpoint
name='addnumber'
implementation='bam.ws.api.Addnumber'
url-pattern='/addnumber'/>
<endpoint
name='mapsender'
implementation='bam.adapter.MapSender'
url-pattern='/mapsender'/>
</endpoints>


6. war파일 생성
Compile 클래스, WSDL문서, xml파일을 다음 디렉토리 구조로 배치하여 war파일로 묶는다.
WAR_HOMEclassescompile 클래스
wsdlAddnumberService.wsdl
AddnumberService_schema1.xsd
MapSenderService.wsdl
MapSenderService_schema1.xsd
web.xml
sun-jaxws.xml


생성된 war 파일은 JBOSS_HOME/server/default/deploy 디렉토리에 복사하여 디플로이한다.
7. WebService 정상 배포 확인
정상적으로 배포되었다면 아래주소로 들어가면 확인 가능하다.
http://localhost:8080/equusSpikeStepOne/addnumber
http://localhost:8080/equusSpikeStepOne/mapsender
1. http://localhost:8080 è jboss web port
2. equusSpikeStepOne è 특별히 지정하지 않았다면 war파일 명칭
3. addnumber, mapsender è sun-jaxws.xm파일에서 지정한 url-pattern
Sample Client 개발
1. Client Library 생성 wsimport
   wsimport -d . -p addumber.client http://localhost:8080/equusSpikeStepOne/addnumber?wsdl
wsimport -d . -p mapsender.client http://localhost:8080/equusSpikeStepOne/mapsender?wsdl
wsimport 자세한 사용법은 “wsimport”을 쳐보세요
2. 생성된 Client Library을 이용하여 Client 프로그램 작성
AddnumberClient
package bam.ws.api.client;
import java.util.List;
import addumber.client.Addnumber;
import addumber.client.AddnumberService;
import addumber.client.ParameterVO;
import addumber.client.ReturnSVOS;
public class AddnumberClient {
public static void main (String[] args) {
Addnumber port = new AddnumberService().getAddnumberPort();
int number1 = 10;
int number2 = 20;
ReturnSVOS vo = port.addNumberVO(number1, number2);
printVO(vo);
List<Object> list = port.addNumberList(number1, number2);
printVO((ReturnSVOS)list.get(0));
ParameterVO pvo = new ParameterVO();
pvo.setNumber1(10);
pvo.setNumber2(20);
ReturnSVOS prvo = port.addNumberParameterVO(pvo);
printVO(prvo);
}
private static void printVO(ReturnSVOS vo) {
System.out.println(vo.getName());
System.out.println(vo.getNow());
System.out.println(vo.getRtnInt());
System.out.println(vo.getRtnLog());
System.out.println(vo.getRtnDouble());
}
}


MapSenderClient
package bam.ws.api.client;
import java.util.List;
import mapsender.client.MapSender;
import mapsender.client.MapSenderService;
import mapsender.client.ParentVO;
import mapsender.client.ParentVO.Childs;
import mapsender.client.ParentVO.Childs.Entry;
public class MapSenderClient {
public static void main (String[] args) {
MapSender port = new MapSenderService().getMapSenderPort();
ParentVO vo = port.getPerson();
Childs cds = vo.getChilds();
List<Entry> list = cds.getEntry();
for(Entry o : list) {
System.out.println(o.getValue().getName());
}
}
}


Map Type은 Client프로그램에서는 Map Type으로 사용되지 않고 Entry로 사용된다.

댓글 없음:

댓글 쓰기

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...