자바 플랫폼은 XML 데이터 스트림을 파싱하고 생성하기 위해 강력한 API를 제공한다. XML스트림을 메모리의 Document Object Model (DOM) 트리로 파싱하기 위해 JAXP를 사용하는 방법은 "Creating Parsers with JAXP."를 참고하기 바란다. 또한 DOM 트리를 XML스트림으로 바꾸기 위해 java.xml.transform.Transformer 인터페이스를 사용하는 방법은 "Transforming XML With XSL."를 참고하자. 만약 이러한 내용을 이미 알고 있었다고 하더라도 코드의 일정 부분만을 이해하고 있었던 것이 아닌가? 데이터를 직접 XML로 작성해 본 적이 있는가?
이번 글은 임의의 데이터 스트럭처로부터 XML을 생성하기 위해 자바 플랫폼에서 표준 XML 조작 기능을 사용하는 방법에 대해 설명한다. 이번 팁은 JAXP 와 XSL 변환에 관한 기본 지식을 갖춘 개발자들을 대상으로 설명하게 될 것이며, 데이터 스트럭처를 XML로 변환하기 위한 프로그래밍 기법의 예제를 제공한다. 이 예제는 SQL ResultSet 객체의 XML로의 변환을 보여준다. 하지만 어떠한 데이터 스트럭쳐의 경우라도 이와 동일한 기법을 사용할 수 있다.
Println 사용의 위험성
만약 데이터가 이미 파일이나 스트림으로 XML로 작성되었거나 메모리상에 DOM으로 작성되었다면 XML을 파싱하고 생성하기 위해 표준 자바 API이면 충분하다. 하지만 개발자들은 종종 임의의 데이터 스트럭쳐의 상태를 XML로 인코딩해야 할 때가 있다. 이러한 데이터 스트럭쳐는 대부분 DOM 인터페이스를 갖고 있지 않다. 따라서 개발자들은 항상 데이터 스트럭쳐(혹은 스트럭쳐의 일부분)를 println 구문을 이용해서 XML로 나열하는 작업을 직접 코딩해야 한다.
이러한 접근방법은 몇 가지 이유로 인하여 문제점이 많다. 이를 구체적으로 살펴보면,
- XML로 변경하는 것은 많은 println 구문을 수정해야 하는 작업을 동반한다.
- 출력 XML을 재구성하는 것은 어려울 뿐만 아니라 에러를 만들어내기 쉽다.
- 로직이 복잡하다면 XML을 잘 작성하기가 어렵다.
- XML 텍스트 스트림이 아닌 곳에서 가져와야 할 데이터가 필요할 수 있다.
좀 더 나은 접근방법은 데이터 스트럭쳐의 데이터를 DOM 트리로 인코딩하는 것이다. 그리고는 원하는 방식으로 데이터를 재정리하기 위해 XSLT를 사용한다. XSLT스타일시트에 기반한 Transformer 객체는 DOM을 XML으로 인코딩하는 작업 없이 DOM에 노드를 추가, 분류, 제거, 병합, 조작할 수가 있다. 그러면 Transformer는 이러한 변환의 결과를 어떠한 형식의 텍스트 포맷으로도 전환할 수가 있다.
이와 같은 접근 방법은 위에서 나열한 문제점들을 해결할 수 있다. 특히,
- 코드를 수정하는 대신 스타일시트를 변경하는 것만으로 XML 다큐먼트 타입을 생성하거나 수정할 수 있다.
- 스타일시트를 이용해서 출력 포맷을 생성하면 자바 코드 외부에 XML을 구축할 수가 있어 유지를 쉽게 할 수 있다.
- Transformer는 자동적으로 출력 값이 잘 작성되었는지 확인한다.
- Transformer는 XML뿐만 아니라 다른 많은 형식의 출력 값을 생성할 수 있다.
그렇다면 문제는 임의의 데이터 스트럭쳐에서 DOM트리를 어떻게 생성하는 것이냐하는 것이다. 이 팁에서 소개되는 샘플 코드는 SQL ResultSet를 DOM 트리로 변환하기 위해 표준 Java XML APIs를 사용하는 방법을 보여준다. 그리고는 스타일시트를 사용해서 그 결과를 XML이나 HTML로 변환한다. 사용자 고유의 데이터 스트럭쳐를 DOM트리로 인코딩하기 위해 동일한 기법을 사용할 수도 있다.
가짜 파서 이용하기
샘플 코드는 May2004Servlet 서블릿을 포함한다. 이 서블릿은 임의의 SQL쿼리를 실행하고 그 결과를 DOM트리로 변환하며, XML형식으로 그 결과를 작성하는 getXML메소드를 갖는다. 또한 SQL ResultSet를 받아서 이를 XML로 변환하는 resultSetToXML메소드도 갖는다.
resultSetToXML가 어떻게 작동하는지를 이해하기 위해서는 DOM 파서의 작동 원리를 반드시 먼저 이해해야 한다. 대부분의 DOM 파서는 XML 소스 스트림을 스캔하기 위해서 SAX 파서를 사용한다. SAX 파서는 XMl 입력 텍스트를 스캔하고 태그의 시작이나 끝점 혹은 애트리뷰트의 출현 등 특정 단어를 만났을 때 리스너에게 이벤트를 보낸다.
DOM 파서는 일련의 SAX 파서 콜백에 대한 응답으로 DOM 트리를 구축하는 콜백 메소드를 정의한다. 샘플 코드에서는 DOM 파서가 실제로는 데이터 스트럭쳐를 분석함에도 불구하고 이것이 텍스트를 스캐닝하는 것처럼 행동하여 DOM 파서를 속인다.
컨셉은 처음에는 조금 이해하기 어려울 수 있다. 대부분의 경우 개발자들은 프레임웍 이벤트에 대한 응답으로 콜백 메소드와 프레임웍 콜을 정의한다. 하지만 이 예제의 샘플코드에서는 반대로 작동한다. 콜백 기능을 작성하는 대신 코드는 DOM 파서에 의해 제공되는 콜백 매소드를 호출하고, DOM 파서는 트리를 구축한다. 다음과 같이 호출을 간단하게 생성하는 parse 메소드를 가정해보자.
startDocument();
startElement("A");
startElement("B");
endElement("B");
endElement("A");
endDocument();
위와 같은 일련의 이벤트에서 DOM파서는 다음과 같은 XML 다큐먼트에 응답하는 DOM 트리를 생성한다.
<?xml version="1.0" encoding="utf-8"?> <a><b/></a>
따라서, 데이터의 DOM 트리를 구축하라는 명령을 DOM 파서에 하고자 한다면, 데이터 스트럭쳐는 분석하지만 SAX 파서의 형식을 한 가짜 SAX 파서 클래스를 하나 작성하기만 하면 된다. 그리고 나서, 가짜 파서 클래스를 DocumentBuilder에 보여주자. DocumentBuilder는 사용자를 위해 DOM을 구축하게 될 것이다. 그러면 DOM은 XML에 열거되거나 혹은 다른 사용을 위해 변환되게 된다.
임의의 데이터스트럭쳐를 XML로 변환하기 위해 Transformer를 설정하는 방법을 살펴보자.
소스 코드
ResultSet를 XML로 변환하는 resultSetToXML 메소드 내의 코드를 보자.
protected void resultSetToXML(OutputStream out,
ResultSet rs,
String stylesheet)
throws IOException, ServletException {
// Create reader and source objects
SqlXMLReader sxreader = new SqlXMLReader();
SqlInputSource sis = new SqlInputSource(rs);
이 코드는 새로운 두개의 클래스 SqlXMLReader와 SqlInputSource를 정의하고 있다. SqlXMLReader 클래스는 SAX 파서를 위한 XML 리더이다. (왜냐하면 SqlXMLReader이 XMLReader를 구현하기 때문이다.) SqlInputSource클래스는 XML 리더를 사용하기 위해서 ResultSet객체의 복사본을 포함하고 있다. SqlXMLReader의 작동원리를 살펴보자. resultSetToXML의 다음 2줄의 코드는 출력값으로 DOM트리를 생성하기 위해 파서를 설정한다.
// Create SAX source and StreamResult for transform
SAXSource source = new SAXSource(sxreader, sis);
StreamResult result = new StreamResult(out);
위의 코드는 XML 리더 (SqlXMLReader)를 InputSource (SqlInputSource)와 결합시키는 SAXSource 객체를 생성한다. 또한 이 코드는 XSLT Transformer 가 XML 스트림으로 결과 값을 생성시키기 위해 사용하는 StreamResult를 생성한다.
코드의 이 시점에서 실제로 변환이 일어나는 데이터는 없다. 대신 리더의 다음 부분은 XML 소스를 읽어 들이기 위한 Transformer 객체를 설정하고 결과값을 스트림으로 변환한다.
// Perform XSLT transform to get results.
// If "stylesheet" is NULL, then use identity
// transform. Otherwise, parse stylesheet and
// build transformer for it.
try {
// Create XSLT transformer
TransformerFactory tf =
TransformerFactory.newInstance();
Transformer t;
if (stylesheet == null) {
t = tf.newTransformer();
} else {
// Read XSL stylesheet from app archive
// and wrap it as a StreamSource. Then use
// it to construct a transformer.
InputStream xslstream =
_config.getServletContext().
getResourceAsStream(stylesheet);
StreamSource xslsource =
new StreamSource(xslstream);
t = tf.newTransformer(xslsource);
}
// Do transform
t.transform(source, result);
}
이 코드는 먼저 팩토리 인터페이스인 newInstance 메소드로부터 새로운 TransformerFactory를 받는다. 이 메소드는 애플리케이션 아카이브(WAR file)의 경로로 스타일시트의 이름을 XSLT 스타일시트로 번역한다. 그리고 나서 메소드는 InputStream으로 스타일시트의 내용을 받고 이 스타일시트 내용에 기반하여 Transformer를 형성한다. 만약 스타일시트가 null값을 갖는다면 디폴트 identity transformer를 생성한다. identity transformer는 DOM을 다른 형태로 변환하지 않고서도 입력 DOM트리를 결과값(이 경우에는 XML스트림)으로 간단하게 변환할 수 있다.
마지막 라인을 보면 이를 알 수 있다.
// Do transform
t.transform(source, result);
이 코드는 transformer에게 SAXSource(SAXSource 는 ResultSet 로부터 이벤트를 만들어내는, 이미 생성된 바 있는 메소드이다.)를 읽어 들이도록 명령하고, 입력 값을 변환하며, 이의 결과값을 StreamResult 객체에 작성한다.
그렇다면 DocumentBuilder 와 DOM 트리는 어떠한가? Transformer는 자체적으로 DOM트리를 구축한다. 또한 DOM 트리를 생성하기 위해 공급되는 SAXSource 객체를 사용하여 필요하다면 변환을 수행하며 StreamResult에 DOM 트리의 결과값을 열거하게 된다.
이제 파서와 transformer가 어떻게 함께 작동하는지 대강의 아웃라인을 이해하였다. 그렇다면 ResultSet로부터 SAX 이벤트를 생성하도록 파서에게 명령하는 방법에 대해 알아보자.
ResultSet로부터 데이터 읽기
SAX 파서의 표준 인터페이스는(SAX2의 경우에는 인터페이스의 두번째 버전을 참고)는 XMLReader라고 불린다. 하지만 이러한 이름에도 불구하고, XMLReader는 java.io.Reader를 상속하지 않는다. 대신, DOM 파서가 그것의 데이터 소스로부터 콜백을 받기 위해 사용하는 메소드를 정의한다.
이미 언급했듯이, ResultSet로부터 이벤트를 생성하는 클래스는 SqlXMLReader이다. SqlXMLReader의 메소드들은 이번 예제와 별 상관이 없기 때문에 대부분이 비어있다. 메소드 파스(Method parse)가 사실 대부분의 일을 한다.
parse 메소드의 첫번째 부분을 분석하기 위해 ResultSet를 받는다.
// Get result set from SqlInputSource. SqlInputSource sis = (SqlInputSource)is; ResultSet rs = sis.getResultSet();
DOM 파서가 이 메소드를 받기 위해 ContentHandler를 제공한 적이 없다면, 이는 에러이다.
if (_handler == null) {
throw new SAXException("No XML ContentHandler");
}
위의 코드는 결과 XML의 태그명에 ResultSet의 컬럼 이름을 사용한다.
// Get information about result set ResultSetMetaData md = rs.getMetaData(); int nColumns = md.getColumnCount(); int iRow = 0;
이 코드는 파서에게 다큐먼트가 시작되고 있다고 알려주기 위해 콜백을 수행하고, 태그명 "result"를 갖는 다큐먼트의 첫번째 구성요소인 다큐먼트 루트를 시작한다.
// Send startDocument and startElement events // to handler _handler.startDocument(); _handler.startElement(uri, docroot, docroot, attrs);
그러면 코드는 태그명과 텍스트, 가독성을 위한 빈칸들을 출력하면서 결과 세트를 반복하여 출력한다. 각 행을 위해 "row"라고 불리는 요소가 생성되고, 각 행의 열을 위해서는 태그명을 열의 이름으로 갖는 요소가 생성된다.
while (rs.next()) {
// Output "row" tag
_handler.startElement(uri, "row", "row", attrs);
outputIgnorableWhitespace("\n");
String s;
for (int i = 1; i <= nColumns; i++) {
String tag = md.getColumnName(i);
_handler.startElement(uri, tag, tag, attrs);
s = rs.getString(i);
if (s == null) {
s = "";
}
outputString(s);
_handler.endElement(uri, tag, tag);
outputIgnorableWhitespace("\n");
}
_handler.endElement(uri, "row", "row");
outputIgnorableWhitespace("\n");
}
마지막으로, 이 코드는 파서에게 다큐먼트 루트를 닫고 다큐먼트를 끝낼 것을 명령한다.
_handler.endElement(uri, docroot, docroot);
outputIgnorableWhitespace("\n");
_handler.endDocument();
샘플 코드에서는 지리 정보의 샘플 데이터베이스를 이용하고 있다. 사용자는 인터페이스에 쿼리를 보내기 위해 샘플 코드의 메인 페이지를 이용할 수 있다.
서블릿은 다음 XML 다큐먼트를 생성하기 위해 resultSetToXML을 사용한다.
<?xml version="1.0" encoding="UTF-8"?> <results><row> <COUNTRY_COUNT>231</COUNTRY_COUNT> </row> </results>
결과가 XML형식으로 보이지 않는다면 브라우저에서 View Source를 선택하여 실제 XML텍스트를 볼 수 있다.
비록 이 예제가 특정하게 ResultSet으로부터 XML 다큐먼트를 생성하는 방법을 보여주고 있기는 하지만 사용자 고유의 데이터 스트럭쳐를 XML로 변환할 때도 이와 동일한 기법을 이용할 수가 있다.
"Java EE" 카테고리의 다른 글
- Attach API (댓글 18개 / 트랙백 1개) 2007/09/03
- JSP 2.0 EXPRESSION LANGUAGE (댓글 1개 / 트랙백 0개) 2004/02/05
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- JAX-WS를 이용한 웹 서비스 개발 (댓글 1개 / 트랙백 0개) 2006/01/18
- EJB 2.1로 메시지 구동 빈 이용하기 (댓글 1개 / 트랙백 0개) 2005/05/18
- EclipseLink를 사용하여 JPA에서 반복 불가능한 읽기 방지 (댓글 0개 / 트랙백 1개) 2008/07/09
- 컴포넌트 시스템과 클래스 로더 경계 (댓글 1개 / 트랙백 0개) 2004/10/05
- JAX-WS Dispatch 및 Provider API를 이용한 문서 처리 (댓글 4개 / 트랙백 0개) 2006/09/15
- POJO를 Persistent Entity로 변환하기 (댓글 1개 / 트랙백 0개) 2005/12/27
- SAAJ 소개 (댓글 1개 / 트랙백 0개) 2005/06/08
댓글을 달아 주세요
좋은 정보 감사해요~
2007/09/19 05:46