2010년 5월 15일 토요일

Json 처리를 위한 XStream

XStream site: http://xstream.codehaus.org
Jettison site: http://jettison.codehaus.org/

XStream과 Jettison 모두 codehaus 의 오픈소스다.
결론적으로 말하면 XStream에서 JSON처리를 위해 Jettison을 사용한다.
우선 두 오픈소스 프로젝트에 대해서 간략히 살펴보자.

XStream


XStream is a simple library to serialize objects to XML and back again.

XStream 사이트에 소개된 말이다. 간단히 말하면 Java Object와 XML을 상호변환하기 위한 심플한 라이브러리란다. Java Object와 XML 변환은 XStream 말고도 자바 표준 API인 JAX-B나 Bea에서 아파치에 기증한 XMLBeans등이 있지만 XStream도 그런 기능을 한단다. 하지만 심플하다고 소개한다. JAXB가 매핑을 하기 위해서 (annotation을 사용한다고 해도) 이래저래 좀 복잡하기는 하다.

Features를 살펴보면 좀 더 자세히 알 수 있다. 오해의 소지를 줄이기 위해서 원문을 그대로 옮겨 적는다.
Features

  • Ease of use. A high level facade is supplied that simplifies common use cases.

  • No mappings required. Most objects can be serialized without need for specifying mappings.

  • Performance. Speed and low memory footprint are a crucial part of the design, making it suitable for large object graphs or systems with high message throughput.

  • Clean XML. No information is duplicated that can be obtained via reflection. This results in XML that is easier to read for humans and more compact than native Java serialization.

  • Requires no modifications to objects. Serializes internal fields, including private and final. Supports non-public and inner classes. Classes are not required to have default constructor.

  • Full object graph support. Duplicate references encountered in the object-model will be maintained. Supports circular references.

  • Integrates with other XML APIs. By implementing an interface, XStream can serialize directly to/from any tree structure (not just XML).

  • Customizable conversion strategies. Strategies can be registered allowing customization of how particular types are represented as XML.

  • Error messages. When an exception occurs due to malformed XML, detailed diagnostics are provided to help isolate and fix the problem.

  • Alternative output format. The modular design allows other output formats. XStream ships currently with JSON support and morphing.



간단히 한글로 옮겨 보겠다.

  • 사용 편이성 : 일반적인 유즈케이스를 단순화시킨 고수준의 facade를 제공한다.

  • mapping 설정이 필요없다 : 대부분의 객체들은 별도의 매핑이 없이도 serialize할 수 있다.

  • 성능: 처리속도가 빠르고 적은 메모리 사용은 XStream 설계의 핵심 부분이다. 이것은 복잡한 객체 구조도 빠르게 처리할 수 있다. 이부분은 처리속도는 아주 빠르지만, CPU와 메모리 사용량이 많은 XMLBeans와 비교하면 눈에 띄는 장점으로 판단된다.

  • 깨끗한 XML: 중복되는 정보도 없고, 사람이 읽기도 용이하고 Java serialization보다도 간결한다.

  • 객체를 수정할 필요가 없다: 내부 필드를 serializable하게 변경할 필요도 없고, private이나 final로 변환할 필요도 없다. non-public inner class들도 지원하며 심지어는 디폴트 생성자도 필요 없다.

  • 복잡한 객체 구조도 완벽하게 지원한다: Object graph는 객체가 하위 객체를 포함하는 관계를 나타내는 말이므로 객체 구조라고 표현하겠다. 중복 참조도 지원한다. 순환참조도 지원한다.

  • 타 XML API와 통합할 수 있다.

  • 변환 전략을 커스터마이즈 할 수 있다.

  • 에러 메시지: 적절하지 않은 XML로 인해 에러가 발생할 경우 문제를 해결할 수 있는 상세한 에러 메시지가 제공된다.

  • 타 포맷으로 출력할 수 있다: XStream은 현재 JSON과 morphings를 처리할 수 있다.



나는 가장 마지막 특징인 JSON 처리를 위해 XStream에 관심을 갖고 있다. 처리 방식이 StAX인것도 호감이 간다.

XStream은 변환(Transport), 저장(Persistence), 환경설정(Configuration), 단위테스트(Unit Tests)등의 용도로 사용한다.


Jettison


A JSON StAX Implementaion. JSON처리용 StAX 구현라이브러리 쯤으로 해석하면 될 것 같다.
Jettison은 StAX와 DOM이 XML을 읽고 쓰는 것처럼 JSON을 읽고 쓸 수 있는 Java API모음이다.
Jettison은 CXF와 같은 웹서비스 프레임웍에서 JSON 기반의 웹서비스를 가능하게 하고, XStream과 같은 XML serialization 프레임웍에서 JSON변환을 가능하게 한다.

프로젝트 소개는 이정도로 마무리 할까 한다.

xstream을 다운 받으면 jettison 라이브러리가 함께 포함되어 있다.
xstream에 포함되어 있는 jar파일들을 클래스패스에 추가하고 아래의 소스코드를 실행해 보면 사용법은 그리 어렵지 않다.


java object 객체



package xstream.sample;

import java.util.List;

public class PurchaseOrder {
Customer customer;
List<Product> items = null;
ShippingInfo shippingInfo;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public List<Product> getItems() {
return items;
}
public void setItems(List<Product> items) {
this.items = items;
}
public ShippingInfo getShippingInfo() {
return shippingInfo;
}
public void setShippingInfo(ShippingInfo shippingInfo) {
this.shippingInfo = shippingInfo;
}
}


package xstream.sample;

public class Customer {
String customerId;
String email;
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}


package xstream.sample;

public class Product{
private String productId;
private double price;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}


package xstream.sample;

public class ShippingInfo {
String address;

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}
}




실행 코드



package xstream.sample;

import java.util.ArrayList;

import org.junit.Test;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class XstreamSample {

@Test
public void testXStream() throws Exception {
PurchaseOrder po = new PurchaseOrder();

Customer customer = new Customer();
customer.setCustomerId("cid1");
customer.setEmail("email@gmail.com");
po.setCustomer(customer);

ShippingInfo shippingInfo = new ShippingInfo();
shippingInfo.setAddress("address1");
po.setShippingInfo(shippingInfo);

ArrayList<Product> items = new ArrayList<Product>();
Product item1 = new Product();
item1.setProductId("pid1");
item1.setPrice(100);
items.add(item1);

Product item2 = new Product();
item2.setProductId("pid2");
item2.setPrice(200);
items.add(item2);

po.setItems(items);

XStream xstream4XML = new XStream();
xstream4XML.alias("purchaseOrder", PurchaseOrder.class);
xstream4XML.alias("customer", Customer.class);
xstream4XML.alias("product", Product.class);
xstream4XML.alias("shipping", ShippingInfo.class);

System.out.println("XML RESULT");
System.out.println(xstream4XML.toXML(po));


XStream xstream4JSON = new XStream(new JettisonMappedXmlDriver());
xstream4JSON.alias("purchaseOrder", PurchaseOrder.class);
xstream4JSON.alias("customer", Customer.class);
xstream4JSON.alias("product", Product.class);
xstream4JSON.alias("shipping", ShippingInfo.class);

System.out.println();
System.out.println("JSON RESULT");
System.out.println(xstream4JSON.toXML(po));



}
}



콘솔화면 출력

XML RESULT
<purchaseOrder>
<customer>
<customerId>cid1</customerId>
<email>email@gmail.com</email>
</customer>
<items>
<product>
<productId>pid1</productId>
<price>100.0</price>
</product>
<product>
<productId>pid2</productId>
<price>200.0</price>
</product>
</items>
<shippingInfo>
<address>address1</address>
</shippingInfo>
</purchaseOrder>

JSON RESULT
{"purchaseOrder":{"customer":{"customerId":"cid1","email":"email@gmail.com"},"items":{"product":[{"productId":"pid1","price":100},{"productId":"pid2","price":200}]},"shippingInfo":{"address":"address1"}}}



사용하기 편하고 간단하며 깨끗한 XML이 출력되고 JSON도 지원되는 것 같다. ^^;;

2010년 5월 14일 금요일

Java - Groovy 연동

Groovy Homepage: http://groovy.codehaus.org/

Groovy Eclipse plugin : eclipse 3.5 http://dist.springsource.org/release/GRECLIPSE/e3.5/


Java 어플리케이션에서 groovy 스크립트를 호출하는 방식은 아래와 같다.

  • groovy 메서드 호출

  • 인터페이스를 구현한 groovy 객체의 메서드 호출

  • groovy 스크립트 엔진 호출



groovy 메서드 호출

아래와 같이 groovy 클래스를 작성한다.
groovy-eclipse를 설치하면 New 메뉴를 이용하여 groovy 클래스를 생성할 수 있다.



package groovy.sample

class Greetings {
static void main(def args) {
def mygreetings = "Hello Groovy World"
println mygreetings
}
}



위와 같이 작성한 후 Greetings.groovy 파일을 선택한 후 우측 마우스 버튼을 클릭하여 컨텍스트 메뉴에서 Run As 하위의 Groovy Script를 호출한다. groovy의 main메서드는 java의 main메서드와 기능적으로 일치한다.
위 groovy 스크립트를 호출하면 콘솔화면에 Hello Groovy World라고 출력된다.
위 스크립트를 자바 클래스에서도 호출할 수 있다.

JUnit 4 스타일의 테스트 메서드를 작성하면 아래와 같다.


package groovy.sample.test;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;

import java.io.File;

import org.junit.Test;

public class GroovyTest {

@Test
public void testGreetings() throws Exception {
ClassLoader parent = this.getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = loader.parseClass(new File("src/groovy/sample/Greetings.groovy"));
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
Object[] args = {};
groovyObject.invokeMethod("main", args);
System.out.println("success");
}

}


위 코드에서 한가지 주의할 점은 parseClass 메서드에 파일명을 문자열로 주면 안된다는 것이다.
parseClass의 파라미터에 문자열이 전달되는 경우는 groovy script의 내용이 전달되는 경우다.
위 메서드를 실행하면 콘솔화면에 아래와 같이 출력된다.

Hello Groovy World
success


인터페이스를 구현한 groovy 객체 호출
아마도 실제 개발환경에서 많이 사용되는 케이스가 아닐까 생각한다. 인터페이스 뿐만 아니라 추상클래스를 상속받아 구현할 수도 있다.

인터페이스 예제

package mybiz.service;

public interface MyService {
String sayYourName();
}



groovy 구현 클래스 1

package groovy.sample

import mybiz.service.MyService;

class GroovyService implements MyService {

/* (non-Javadoc)
* @see mybiz.service.MyService#sayYourName()
*/
@Override
public String sayYourName() {
return "My name is groovy";
}
}



groovy 구현 클래스 2

package groovy.sample

import mybiz.service.MyService;

class DukeService implements MyService {

/* (non-Javadoc)
* @see mybiz.service.MyService#sayYourName()
*/
@Override
public String sayYourName() {
return "My name is duke";
}
}



테스트 케이스

package groovy.sample.test;

import java.io.File;

import groovy.lang.GroovyClassLoader;

import mybiz.service.MyService;

import org.junit.Test;

public class ServiceTest {

@Test
public void testService() throws Exception {
ClassLoader parent = this.getClass().getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class groovyClass = null;
groovyClass = loader.parseClass(new File("src/groovy/sample/GroovyService.groovy"));
MyService groovyService = (MyService) groovyClass.newInstance();
System.out.println(groovyService.sayYourName());

groovyClass = loader.parseClass(new File("src/groovy/sample/DukeService.groovy"));
MyService dukeService = (MyService) groovyClass.newInstance();
System.out.println(dukeService.sayYourName());

}
}



위와 같이 실행하면 콘솔화면에 아래와 같이 출력된다.

My name is groovy
My name is duke


스크립트 엔진을 이용하여 groovy script 실행
velocity와 같이 문자열을 치환하는 스크립트를 실행하는 예제를 살펴보자.

groovy 스크립트를 src/groovy/sample/hello.groovy 로 아래와 같이 생성한다

message = "hello, ${name}!"
introduction = "my name is ${name}. I'm ${age} years old"




package groovy.sample.test;

import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;

import org.junit.Test;

public class ScriptEngineTest {

@Test
public void testGroovyScriptEngine() throws Exception {
String[] urls = new String[] { "src/groovy/sample" };
GroovyScriptEngine scriptEngine = new GroovyScriptEngine(urls);
Binding binding = new Binding();
binding.setVariable("name", "duke");
binding.setVariable("age", 25);
scriptEngine.run("hello.groovy", binding);
System.out.println("message: " + binding.getVariable("message"));
System.out.println("introduction: " + binding.getVariable("introduction"));
}
}



위 테스트 케이스를 실행하면 콘솔화면에 아래와 같이 출력된다.
message: hello, duke!
introduction: my name is duke. I'm 25 years old

2010년 5월 2일 일요일

Spring에서 Velocity 사용하기

Velocity는 템플릿 엔진으로 사용하는 오픈소스 프레임웍이다.
메일 메시지를 작성할 때나 XML 파일, 심지어는 자바 소스 코드를 생성할 때도 사용하면 유용하다.
Spring에서는 Velocity를 대부분 View (MVC)에서 사용하는데 반드시 View 용도로만 사용할 필요는 없다.

이번 글은 Velocity View를 설정하기 위한 것은 아니다.
템플릿을 사용할 경우 velocity를 이용하는 방식에 대한 것으로 보다 일반적인 사용 시 설정 방법을 이야기 한다.

스프링 빈을 설정 파일을 이용하지 않고 애노테이션 기반 설정을 사용한다.
스프링 빈 애노테이션 기반 설정에 관해서는 기회가 되면 다음에 글을 올리도록 하겠다.

스프링 환경 설정 파일


<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<import resource="classpath:META-INF/amf/amf-db.xml"/>
<import resource="classpath:META-INF/amf/amf-dao.xml"/>
<import resource="classpath:META-INF/amf/amf-service.xml"/>
<import resource="classpath:META-INF/amf/amf-rs.xml"/>
<import resource="classpath:META-INF/amf/amf-ws.xml"/>

<context:component-scan base-package="com.archnal.amf.util"/>

<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
<value>
resource.loader=class
class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
</value>
</property>
</bean>
</beans>




사용 예제



package com.archnal.amf.util.service;

import java.io.StringWriter;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.stereotype.Service;
import org.springframework.ui.velocity.VelocityEngineUtils;

@Service("mailTemplateService")
public class MailTemplateServiceImpl implements IMailTemplateService {

public static final String WELCOME_MAIL = "velocity/welcome-mail.vm";

@Resource(name="velocityEngine")
private VelocityEngine velocityEngine;
/* (non-Javadoc)
* @see com.archnal.amf.util.service.IMailTemplateService#getMailTemplate(java.util.Map)
*/
@Override
public String getMailTemplate(Map map) throws Exception {
// TODO Auto-generated method stub
StringWriter writer = new StringWriter();
// Map map = new HashMap();
// map.put("userName", "kim");
VelocityEngineUtils.mergeTemplate(velocityEngine, WELCOME_MAIL, map, writer);
System.out.println(writer);
return writer.toString();
}


}




테스트 클래스


package com.archnal.amf.util.service;

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

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:/spring/context-*.xml" })
public class MailTemplateServiceTest {

@Resource(name="mailTemplateService")
IMailTemplateService mailTemplateService;

@Test
public void testGetMailTemplate() throws Exception{
Map model = new HashMap();
model.put("userName", "Kim");

String result = mailTemplateService.getMailTemplate(model);

System.out.println(result);
}
}