메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Jakarta Digester 학습하기

한빛미디어

|

2002-11-05

|

by HANBIT

10,968

저자: 필립(Philipp K. Janert Ph.D), 역 김대곤

XML문서를 문서와 대응되는 계층구조를 가진 자바 빈 객체로 변환하는 것은 분명 빈번하게 요구되는 작업 중의 하나이다. 필자가 이전에 논했었던 "Simple XML Parsing with SAX and DOM"라는 기사에서 SAX와 DOM이라는 표준 API를 사용하여 이러한 작업을 수행하는 방법에 대해 기술하였다.

SAX과 DOM이 강력하고, 유연함에도 불구하고, 실제로 이 두 가지 API는 특정한 작업을 간단하게 수행하기에는 너무 저수준인 API이다. 뿐만 아니라 unmarshalling 작업(XML문서를 객체로 변환하는 작업) 자체에 상당한 코딩을 해주어야 한다. 즉, SAX를 사용할 때에는 반드시 parse-stack이 관리되어야 하며, DOM를 사용할 때에는 반드시 DOM-tree를 사용해야 한다는 말이다.

Unmarshalling 작업은 Apache Jakarta Commons Digester 프레임워크가 해결할 수 있는 부분이다.

Jarkarta Digester 프레임워크

Jakarta Digester 프레임워크는 Jakarta Struts Web toolkit의 일부로 시작하여 발전되었다. 원래는 주요한 struts-config.xml를 처리하기 위해 개발되었으나 보다 일반적으로 유용하다는 것이 인식되어, Jakarta Commons 프로젝트로 옮기게 되었다. 이 프로젝트는 재사용 가능한 자바 컴포넌트의 리포지토리(repository)를 제공하는 것을 목표로 하고 있다. 최신 버전인 Digester1.3이 2000년 8월 13일에 발표되었다.

Digester 클래스를 통해 애플리케이션 프로그래머는 파서가 XML 문서의 간단한 특정 패턴을 만났을 때, 필요한 작업이 자동적으로 수행되게 해준다. Digester 프레임워크는 미리 정의된 10개의 규칙을 가지고 있다. 이러한 규칙들은 XML문서를 unmashalling하는데 필요한 대부분의 작업들을 포함하고 있지만 필요할 때마다 각 사용자들이 직접 규칙을 정의할 수도 있게 되어있다.

예제 문서와 빈즈

이제 보게 될 예제에서는 이전 기사에서 사용한 XML 문서을 unmarshall할 것이다.




   
      Author 1
      Title 1
   

   
      Author 2
      His One Book
   

   
      Mag Title 1

      
Some Headline
Another Headline
Author 2 His Other Book Mag Title 2
Second Headline
자바 빈(Java Bean) 클래스도 한가지 주요한 변화를 제외하고는 XML 파일과 동일하다. 이전 기사에서 빈 클래스들을 하나의 소스 파일로 정의하기 위해 package를 선언하였다. 그렇지만 Digester 프레임워크를 사용할 때에는 이러한 방법을 사용할 수 없다. 모든 클래스는 public으로 선언되어야 한다. public으로 선언하는 것은 자바 빈즈의 규약에서도 요구되는 부분이다.
import java.util.Vector;

public class Catalog {
   private Vector books;
   private Vector magazines;

   public Catalog() {
      books = new Vector();
      magazines = new Vector();
   }

   public void addBook( Book rhs ) {
      books.addElement( rhs );
   }
   public void addMagazine( Magazine rhs ) {
      magazines.addElement( rhs );
   }

   public String toString() {
      String newline = System.getProperty( "line.separator" );
      StringBuffer buf = new StringBuffer();

      buf.append( "--- Books ---" ).append( newline );
      for( int i=0; i

public class Book {
   private String author;
   private String title;

   public Book() {}

   public void setAuthor( String rhs ) { author = rhs; }
   public void setTitle(  String rhs ) { title  = rhs; }

   public String toString() {
      return "Book: Author="" + author + "" Title="" + title + """;
   }
}


import java.util.Vector; public class Magazine { private String name; private Vector articles; public Magazine() { articles = new Vector(); } public void setName( String rhs ) { name = rhs; } public void addArticle( Article a ) { articles.addElement( a ); } public String toString() { StringBuffer buf = new StringBuffer( "Magazine: Name="" + name + "" "); for( int i=0; i public class Article { private String headline; private String page; public Article() {} public void setHeadline( String rhs ) { headline = rhs; } public void setPage( String rhs ) { page = rhs; } public String toString() { return "Article: Headline="" + headline + "" on page="" + page + "" "; } }
패턴과 규칙

Digester 클래스는 패턴(pattern)과 규칙(rule)을 기준으로 입력되는 XML 문서를 처리한다. 패턴들은 XML 문서 Tree 구조의 이름과 위치에 근거해서 XML Element와 일치해야 한다. 매칭 패턴을 정의하는 문법은 Xpath의 매칭 패턴과 유사하다. Catalog패턴은 Element와 일치하고, catalog/book 패턴은 Element 안에 포함된(문서의 다른 부분에서 사용되지 않는) Element와 일치한다.

모든 패턴은 절대 경로이다. 즉 루트(root) Element에서 시작되는 전체 경로가 명시되어야 한다. *(와일드카드 문자)가 사용된 패턴만이 예외로 인정된다. 즉, */name 패턴은 문서에 어느 곳에 위치한 Element와 매칭될 수 있다. 또한 모든 경로가 절대경로이기 때문에 root Element를 특별히 명시할 필요가 없다.

Digester 클래스는 정의된 패턴을 만나면 패턴과 연관된 작업을 수행한다. 이때, Digester 프레임워크는 물론 SAX 파서을 사용한다. 실제로 Digester 클래스는 org.xml.sax.ContentHandler를 구현하고 있으며, parse stack를 관리하고 있다. Digester클래스에서 사용하는 모든 규칙(rule)은 org.apache.commons.digester.Rule 클래스를 상속받아야 한다. 이 Rule 클래스는 내부에서 SAX의 ContentHandler의 재귀함수(매칭된 Element가 시작 태그와 종료 태그를 만났을 때 호출되는 begin, end 메소드)와 유사한 메소드들을 사용한다.

Body() 메소드는 매칭된 Element안에 포함된 내용들을 처리하기 위해 호출된다. 마지막으로, finish() 메소드가 있는데, 이것은 종료 태그의 처리가 완료되었을 때, 필요한 마지막 정리작업을 위해 호출된다. 그러나 프레임워크에 포함된 표준 규칙들은 원하는 모든 기능들을 이미 제공하기 때문에 대부분의 애플리케이션 개발자들은 이러한 함수에 대하여 신경쓸 필요가 없다.

XML문서를 unmarshall하기 위해서는 org.apache.commons.digester.Digester 객체를 생성하고, 필요한 경우 객체을 설정하고, 처리하기 원하는 패턴과 규칙들을 정의하고, 마지막으로 XML문서에 대한 참조값을 parse() 메소드에 넘겨준다. 이러한 일련의 작업들은 아래에 있는 DigesterDriver 클래스에 나타나 있다. XML문서의 파일명은 실행시 인자로 넘겨주어야 한다.
import org.apache.commons.digester.*;

import java.io.*;
import java.util.*;

public class DigesterDriver {

   public static void main( String[] args ) {

      try {
         Digester digester = new Digester();
         digester.setValidating( false );

         digester.addObjectCreate( "catalog", Catalog.class );

         digester.addObjectCreate( "catalog/book", Book.class );
         digester.addBeanPropertySetter( "catalog/book/author", "author" );
         digester.addBeanPropertySetter( "catalog/book/title", "title" );
         digester.addSetNext( "catalog/book", "addBook" );

         digester.addObjectCreate( "catalog/magazine", Magazine.class );
         digester.addBeanPropertySetter( "catalog/magazine/name", "name" );

         digester.addObjectCreate( "catalog/magazine/article", Article.class );
         digester.addSetProperties( "catalog/magazine/article", "page", "page" );
         digester.addBeanPropertySetter( "catalog/magazine/article/headline" ); 
         digester.addSetNext( "catalog/magazine/article", "addArticle" );

         digester.addSetNext( "catalog/magazine", "addMagazine" );

         File input = new File( args[0] );
         Catalog c = (Catalog)digester.parse( input );

         System.out.println( c.toString() );

      } catch( Exception exc ) {
         exc.printStackTrace();
      }
   }
}
이 예제에서는 DTD를 사용하여 XML문서의 유효성을 검사할 수 없다. Catalog 문서에 DTD를 지정하지 않았기 때문이다. 패턴과 관련된 규칙들을 직접 정의하였다. ObjectCreateRule은 지정된 클래스의 인스턴스를 생성하여 parse stack에 넘겨준다. SetPropertiesRule은 bean 속성에 현재 Element의 attribute 값을 지정한다. 이 메소드의 첫 번째 인자는 attribute의 이름이고, 두 번째 인자는 속성의 이름을 나타낸다.

SetPropertiesRule이 attribute로부터 값을 읽어온다면, BeanPropertySetterRule은 현재 Element에 안에 있는 raw character data를 가져온다. BeanPropertySetterRule를 사용할 때에는 Bean 속성의 이름을 특별히 정의할 필요가 없다. BeanPropertySetterRule은 특정한 설정이 없는 경우, Element의 이름을 사용하기 때문이다. 위의 예제에서는 catalog/magazine/article/headline에 규칙을 지정하면서 이러한 설정법을 사용하였다. 마지막으로 남은 SetNextRule은 생성한 객체를 parse stack의 처음으로 옮기고, 생성된 객체를 가진 상위 객체의 메소드로 넘겨준다. 이것은 주로 만들어진 Bean를 상위 클래스에 추가할 때 사용된다.

동일한 패턴에 대해 다수의 규칙을 적용할 수도 있다. 이때에는 Digester 클래스에 입력된 순서대로 규칙이 실행될 것이다. 예를 들어 위 프로그램에서 catalog/magazine/article
Element를 다룬 것을 살펴보면 알맞은 Bean 객체를 먼저 생성하고, page의 속성값을 지정하고, 마지막으로 생성된 객체를 상위 객체인 magazine에 추가하였다는 것을 알 수 있다.


Java & XML Data Binding

참고 도서

Java & XML Data Binding
Brett McLaughlin




임의의 함수 호출

Bean의 속성을 설정하는 것 뿐만 아니라 객체에 정의된 임의의 메소드를 호출할 수도 있다. 이것은 CallMethodRule를 사용하여 메소드의 이름과 필요시 인자의 수와 속성을 넘겨줌으로써 가능하다. CallParamRule를 통해 호출되는 메소드에 파라미터 값을 전달할 수 있다. 이 값은 Element의 속성값, 또는 Element의 데이터에서 가져올 수 있다. 예를 들어 위의 예제에서 BeanPropertySetterRule를 사용하기 보다는, 데이터를 파라미터로 넘기면서, 속성 setter 메소드를 직접 호출하여 동일한 작업을 수행할 수 있다.
digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );
digester.addCallParam( "catalog/book/author", 0 );
첫번째 라인은 호출할 메소드의 이름과 전달되는 인자의 수를 지정하고, 두 번째 라인은 Element의 데이터를 함수의 인자값으로 사용하고, 인자의 array의 첫번째 값으로 전달함을 의미한다. 이 때 Element의 attribute값을 인자로 넘길 수 있다. (digester.addCallParam("catalog/book/author", 0, "author");)

여기서 주의할 점이 하나 있다. Digester.addCallMethod("pattern", "methodName", 0)이 전달되는 인자가 없다는 의미가 아니라는 것이다. 이것은 호출되는 메소드의 인자값을 현재 Element의 데이터 값을 사용하겠다는 의미이다. 그러므로 위의 예제에서 사용한 BeanPropertySetterRule을 다음과 같이 변환할 수 있다.
digester.addCallMethod( "catalog/book/author", "setAuthor", 0 );
인자를 취하는 않는 메소드를 호출하는 경우에는 digester.addCallMethod("pattern", "methodName")를 사용한다.

표준 규칙 요약

다음은 표준 규칙에 대한 간단한 설명이다.

Creational
  • ObjectCreateRule: 객체의 기본 생성자를 사용하여 객체를 생성하고, 이를 stack에 넘겨준다. Element의 처리가 끝날 때 객체는 반환된다. 객체를 지정하는 방법은 Class 객체를 사용하는 법과 전체 클래스의 이름을 사용하는 법이 있다.
  • FactoryCreateRule: 특정 Factory 객체를 사용하여 객체를 생성하고, 이를 스택에 넘겨준다. 기본 생성자를 제공하지 않는 클래스를 사용할 때 유용하다. 이 Factory 클래스는 org.apache.commons.digester.ObjectCreationFactory 인터페이스를 구현해야 한다.
Property Setters
  • SetPropertiesRule: Element의 속성값을 사용하여 top-level 수준의 하나 또는 여러 개의 속성값을 지정한다. Attribute의 값과 속성의 값은 String 배열을 사용하여 전달한다.
  • BeanPropertySetterRule: Element의 데이터를 top-level 수준의 속성값을 지정한다.
  • SetPropertyRule: 현재 XML Element의 attribute과 attribute의 값을 Bean의 속성과 속성값으로 지정한다.
Parent/Child Management
  • SetNextRule: 생성한 객체를 stack에 넘겨주고, 상위 객체에 정의된 동일한 이름의 메소드를 호출한다. 일반적으로 완성된 객체를 상위 객체에 추가할 때 사용한다.
  • SetTopRule: stack의 second-to-top 객체를 top-level 객체로 전달한다. 이것은 Child 객체에서 setParent 메소드가 사용될 경우에 유용하게 사용된다.
Arbitrary Method Calls
  • CallMethodRule: top-level Bean의 임의의 메소드를 호출한다. 호출되는 메소드의 인자의 수는 제한이 없다. CallParamRule에 의해 인자의 값이 전달된다.
  • CallParamRule: 메소드의 인자의 값을 표시한다. 인자의 값은 Element의 attribute나 데이터로부터 얻어올 수 있다. 이 규칙은 정수 인덱스로 지정되는 인자의 위치를 요구한다.
XML에서의 규칙: xmlrules 패키지 사용하기

지금까지 패턴과 규칙을 컴파일 시점에 설정하는 것에 대하여 살펴보았다. 개념적으로는 간단하고, 직접적으로 보이지만 이러한 방법은 불편하다. 전체 프레임워크는 실행시 각 구조와 데이터를 인식하고 처리하기 위한 것인데, 위의 예제에서는 컴파일 시점에서 이미 구조와 데이터 인식 방법이 결정되고 있기 때문이다. 이러한 방식은 많은 코드를 필요로 하며, 프로그램이라기보다는 설정에 가깝다.

org.apahce.commons.digester.xmlrules 패키지는 위에서 언급한 문제를 다루고 있다. DigesterLoader 클래스는 패턴/규칙이 정의된 XML문서를 이용하여, Digester 객체를 생성한다. Digester 클래스를 설정하는 XML파일은 xmlrules 패키지에 포함된 digester-rules.dtd에 유효한 문서여야 한다. 즉, dtd의 규약대로 작성되어야 한다.

아래는 예제 프로그램을 위한 Digester 클래스 설정 XML파일이다. 몇 가지를 살펴보자.

패턴은 두 가지 방법으로 설정될 수 있다. 첫째 규칙을 나타내는 XML의 attribute로 표시될 수 있고, 둘째 Element로 정의될 수 있다. 두 번째 설정방법은 규칙 Element가 포함된 모든 곳에서는 모두 사용할 수 있다. 두 방식은 혼합해서 사용할 수 있으며, Element안에 Element가 올 수 있다.

Element안에 사용된 Element는 XML attribute와 Bean의 속성을 매핑할 때 사용한다.

마지막으로, 현재의 Digester 패키지에서는 설정파일에서 SetPropertySetterRule를 사용할 수 없다. 대신, CallMehtodRule를 사용하여 동일한 작업을 수행할 수 있다.



   
   
      
   

   
      
      
      
      
   

   
      

      

      
         
         
            
         
         
         
      

      
   

실제 작업을 Digester 클래스와 DigesterLoader에서 수행하면 DigesterDriver 클래스는 아주 단순해진다. 이제 DigesterDriver 클래스를 실행하기 위해서는 catalog XML문서와 rules.xml문서를 실행하는 순서대로 인자로 넘겨줘야 한다. 이때 rules.xml 파일은 File 클래스나 org.xml.sax.InputSource 클래스가 아닌 URL을 요구한다.
import org.apache.commons.digester.*;
import org.apache.commons.digester.xmlrules.*;

import java.io.*;
import java.util.*;

public class XmlRulesDriver {
   public static void main( String[] args ) {
      try {

         File input = new File( args[0] );
         File rules = new File( args[1] );

         Digester digester = DigesterLoader.createDigester( rules.toURL() );

         Catalog catalog = (Catalog)digester.parse( input );
         System.out.println( catalog.toString() );
  
      } catch( Exception exc ) {
         exc.printStackTrace();
      }
   }
}
결론

이상으로 Jakarta Commons Digester 패키지에 대한 간단한 설명을 끝내겠다. 물론 더 많은 내용이 있지만. 여기에서 다루지 않았던 하나의 주제는 XML 네임스페이스(Namespace)에 관한 것이다. Digester는 특정 네임스페이스에 속한 Element들에 패턴과 규칙을 정의할 수 있다.

우리는 위에서 Rule클래스를 사용하여 사용자가 직접 작성한 규칙을 정의할 수 있다는 사실을 살펴보았다. 또한 Digester 클래스는 사용자가 정의한 push(), peek(), pop() 메소드를 통해, 개발자로 하여금 parse stack를 직접 수정할 수 있는 기능을 제공한다.

최근에 RSS(Rich-Site-Summary)를 다루는 Digester 클래스를 제공하는 추가 패키지가 제공되었다. 이 패키지는 Javadoc을 사용하여 살펴볼 수 있다.

참고사이트
필립(Philipp K. Janert Ph.D)은 소프트웨어 프로젝트 컨설턴트이자, 서버 프로그래머와 설계자로 활동하고 있다.
TAG :
댓글 입력
자료실

최근 본 책0