IBM Korea Skip to main content
       IBM 홈    |  제품 & 서비스  |  고객지원 & 다운로드  |  회원가입  

아파치 SOAP 유형 매핑, Part 1 : 아파치의 직렬화 API 검토하기
목 차:
SOAP and RPC
Section 5 encoding
Type mapping patterns
Wrapping up and looking ahead
참고 자료
필자 소개
기사에 대한 평가
관련 dW 링크:
웹 서비스 내부, Part 3
developerWorks newsletter 구독하기
US 원문 읽기
Also in the Web services zone:
Tutorials
Tools and products
Articles
Also in the Java zone:
Tutorials
Tools and products
Code and components
Articles
 
여러분 애플리케이션의 데이터 유형을 XML로 변환하는 방법


Gavin Bong
소프트웨어 엔지니어, eUtama Sdn. Bhd.
2002년 4월

SOAP은 애플리케이션 레벨 데이터를 전달하기 위한 간단한 네트워크 프로토콜을 정의한다. 이 프로토콜은 풍부하고 확장 가능한 유형 시스템 덕분에 임의적인 자바 유형을 직렬화된 XML로 쉽게 전달할 수 있다. 아파치 SOAP 툴킷에서 발견되는 유형 시스템 지원에 관한 2편으로 된 시리즈의 1편인 이 글에서 Gavin Bong은 SOAP의 유형 시스템의 이론적인 기초를 소개할 것이다. 여러분은 또한 직렬화와 비직렬화에 대한 SOAP의 프로그램적인 지원에 관해 배울 것이고 툴킷의 내부를 검토하면서 결론을 내릴 것이다. 이 프로세스들이 어떻게 작동하는지를 잘 이해하면 여러분 자신의 분산 시스템 구축에 도움이 될 것이다.

데이터가 전자식으로 교환될 때, 교환의 양 끝은 두 가지 사항에 대해 사전에 동의해야 한다. 상호작용 패턴유형 시스템이 그것이다. 상호작용 패턴은 통신 채널 (예 : point-to-point 대 many-to-one, 혹은 블로킹 대 동기식)의 구조와 관련되어 있다. 반면 유형 시스템은 메시지 인코딩과 해독에 사용될 데이터 포맷을 동의한 것이다.

이 글에서 나는 아파치 SOAP 툴킷에 적용할 수 있는 SOAP의 유형 시스템을 설명할 것이다. 현재의 SOAP 툴킷은 메시징과 RPC 상호작용 유형을 둘 다 지원한다. 이 글은 후자에 초점을 맞출 것이다. 달리 언급되지 않는 한 이 글에서 설명하는 기능들은 2001년에 출시된 아파치 SOAP v.2.2에 적용된다(참고 자료). 적절한 곳에서 나는 그 출시판 이후 발생한 버그 수정이나 인터페이스 변화를 강조하겠다.

용어에 대한 여담으로, 이 시리즈에서 사용되는 이름 공간 접두사 (SOAP과 W3C의 XML 스키마 모두에 대한)와 QNames (qualified names)을 표현 관행에 대해 소개하겠다.

  • SOAP 1.2 작업 초안이 2001년에 출시되었지만, 우리의 논의는 SOAP 1.1의 상세 사항으로 한정될 것이다. 따라서 SOAP-ENV와 SOAP-ENC 접두사는 각각 http://schemas.xmlsoap.org/soap/envelope/http://schemas.xmlsoap.org/soap/encoding 이름공간을 참조할 것이다.
  • xsddhk xsi 접두사는 명확하게 달리 언급되지 않는 한 각각 http://www.w3.org/2001/XMLSchemahttp://www.w3.org/2001/XMLSchema-instance 이름 공간을 참조한다고 가정된다.
  • 외래 URI을 가진 QNames은 다음 포맷으로 작성될 것이다. :
    
    {uri}localpart.
    
    예제 : {http://xml.apache.org/xml-soap}Map

SOAP과 RPC
SOAP에서 RPC 호출을 실행할 때는 웹 서비스 요청자와 제공자간에 두 유형의 메시지가 교환된다. :

  • 원격 메소드에 대한 매개변수 (RPC 요청)
  • 반환값 (PRC 응답)

기술적으로, SOAP 첨부 문서와 SOAP 헤더는 또한 SOAP RPC 호출의 메시지를 구성할 수 있다.; 그러나 우리는 지금은 이들을 무시해도 된다. 메시지는 보통 구어체로 Section 5라고 알려진 기법을 사용하여 인코딩된다. 오늘날 이용할 수 있는 모든 SOAP 프레임워크는 이 인코딩 기법을 지원한다. SOAP 사양이 이를 기본 인코딩으로 지정하지는 않지만 말이다.

Section 5 인코딩은 객체 그래프를 XML로 변환하기 위한 방법론을 설명한다. 방법론이 철저하게 지켜진다면 SOAP XML은 자신의 원래 형태로 재구축될 수 있다. 또다른 인코딩 방식은 W3C의 XML 스키마와 같은 스키마 언어를 사용하여 메시지를 제한하는 것이다. 앞의 방법론에서는 트랜잭션에 관여하고 있는 모든 당사자가 직렬화 규칙에 합의하는 반면 후자에서는 메시지의 문자적인 포맷에 합의한다. 이 시리즈의 2편에서 여러분은 스키마에 의해 제한된 SOAP을 나타내는 예제를 볼 것이다.

Listings 1과 2는 Section 5 인코딩을 상세하게 보여주며 검토하고 있다. Listing 1에 있는 Foo JavaBean이 Listing 2 에서 직렬화된다.

Listing 1. Foo JavaBean

class Foo{
  int i;
  String s;
  public Foo() {}
  ....  /* property mutators and accessors not shown for brevity */  
}

Listing 2. Foo 객체의 SOAP 표현

  <SOAP-ENV:Body>
    <ns1:eatFoo 
      xmlns:ns1="urn:ibmdw:myservice"                
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <fooParam 
        xmlns:ns2="http://foo/type" 
        xsi:type="ns2:Foo">
        <i xsi:type="xsd:int">1000</i>
        <s xsi:type="xsd:string">Hello World</s>
      </fooParam>
    </ns1:eatFoo>
  </SOAP-ENV:Body>

Listing 2의 SOAP XML 인스턴스는 eatFoo 메소드 호출을 URI urn:ibmdw:myservice에 의해 확인된 웹 서비스로 보내는 RPC 호출을 표현한다. fooParam, i, 및 s element는 접근자 (accessor)라고 불린다.; 이들은 값을 담고 있는 컨테이너들이다. 복수 참조되는 접근자는 이 정의에서 제외된다,; 이들은 실제 값을 포함하고 있는 다른 element들을 참조하기 위해 XML Pointer Language (상세 사항은 아래의 참고 자료 참조)와 같은 메커니즘을 채택하는 빈 element들이다.

Listing 2에 많이 나와 있는 xsi:type 속성은 접근자의 하위 유형을 제공한다. 보통 한 웹 서비스 제공자와 요청자는 각 RPC 호출에 대한 매개변수의 데이터 유형을 사전에 합의한다. 이러한 사전 합의는 심지어는 xsi:type 속성이 없어도 SOAP XML 인스턴스를 올바로 비직렬화하기에 충분하다. 그러나 특정 메소드가 문자열과 정수를 모두 받아들이는 경우 (예를 들어, 매개변수는java.lang.Object 매개변수를 받아들이는 유형일 수도 있다.)를 고려해 보자. 이 경우 객체가 올바로 비직렬화되려면 포함된 값의 유형을 표명하기 위해 명확한 xsi:type이 필요하다. 명확한 xsi:type 속성을 가진 접근자를 다형 접근자(polymorphic accessors)라고 부른다.

아파치 SOAP은 모든 접근자를 다형적으로 처리하도록 설계되었다. 버젼 2.2에서도 다형 접근자를 가지는 SOAP XML 인스턴스 생성은 계속되지만 그것 없이도 비직렬화되도록 프로그램 될 수 있다. 나는 이 글의 뒤에서 이것이 어떻게 수행되는지 간략히 설명하겠다.

xsi:type 속성은 QNames을 값으로 취한다. xsi:type 속성이 받아들일 수 있는 값은 다음과 같다.:

XML Schema Part 2 Datatypes 사양에서 나오는 내장된 유형

Apache SOAP은 대부분의 내장된 유형을 지원하고, 다음과 같은 schema Part 2 사양의 모든 버전에서 나오는 유형들과 역 호환된다.

  • 권고안 (2201)
  • 후보 권고안 (2000년 10월)
  • 작업 초안 (1999)

정확성을 위해, 아파치 SOAP은 모든 내장된 유형을 다루지는 않지만, 가장 흔히 사용되는 유형들은 지원한다. 나는 다음 섹션에서 아파치 SOAP에 의해 지원되는, Section5에 의해 인코딩된 유형들의 상세한 목록을 살펴 볼 것이다.

사용자가 정의한 유형을 나타내는 임의적인 QNames

이들은 Listing 2에 있는 ns2:Foo와 같은 복합 유형을 표시하기 위해 사용된다. 이 유형의 직렬화된 형태는 스키마 문서에 의해 , 혹은 아파치 SOAP 내에서처럼 커스텀 (비)직렬자에 의해 정의될 수 있다.

SOAP 1.1 확장 유형

여기에는 배열 멤버의 순서를 표현하는 SOAP-ENC:Array와 base-64에 의해 인코딩된 문자열을 표현하는 SOAP-ENC:base64가 포함된다.

Listing 2encodingStyle 속성은 사용된 직렬화 스키마를 가리키는데 이용된다. 예를 들어, Section 5 인코딩은 URI http://schemas.xmlsoap.org/soap/encoding에 의해 표현된다. 아파치 SOAP은 다음 세 개의 encodingStyles에 대한 지원을 내장하고 있다. : Section 5, XMI, 그리고 literal XML이 그것이다. 사용자 정의 encodingStyle 역시 지원한다.

그러나 아파치 SOAP의 Section 5 인코딩 지원은 포괄적이지 않다. 배열 사이가 비어 있고 부분적으로 전송될 수 있으며 다차원적인 배열에 대한 지원 등 여전히 몇 가지 기능들이 여전히 빠져 있다.

지금까지 나는 데이터 교환의 엔드포인트들이 Section 5로 인코딩된 유형을 신기하게 (비) 직렬화할 수 있을 것임을 암시해 왔다.; 그러나 이 전제는 전달하고 전달받고 있는 엔터티들의 데이터 모델에 대한 대역 밖의 동의에 실제로 의존한다. 이것은 기본적으로 엔드포인트들이 "잘 알려진" 유형들에 동의해야 함을 의미한다. 그러나 여기에 대안이 있다. Section 5의 한계점을 원하지 않는 소프트웨어 개발자들은 스키마 기반의 직렬화를 사용할 수 있다. 이 기법은 요청/응답 메시지에 대한 스키마와 함께 SOAP 서비스의 인터페이스를 공개함으로써 작동한다. 웹 서비스 정의 언어(WSDL)는 현재 이러한 목적을 위한 사실상의 표준이다. 아파치 SOAP은 WSDL을 인식하지 않지만, 아파치 SOAP에서 나온 툴킷인 Axis (참고 자료)는 이를 인식한다.

Section 5 인코딩

SOAP은 Section 5 인코딩에 의해 기술된 SOAP 유형에 대한 어떤 언어 바인딩도 정의하지 않는다. 대신 그 유형들은 자바 프로그래밍 언어와 대부분의 다른 주류 프로그래밍 언어에서 발견되는 일반적인 데이터 유형 중 일부를 모델링하기에 충분할만큼 총괄적이다. 이 섹션에서는 자바 원시 코드와 임의적인 자바 클래스들의 인코딩과 해독에 대한 아파치 SAOP의 지원을 살펴보도록 하겠다.

먼저 용어부터 정의해보자. 직렬화는 자바 객체를 XML 인스턴스로 변환하는 절차이고, 비직렬화는 XML로부터 자바 개체를 재구축하는 절차이다. 아파치 SOAP은 이 활동들을 수행하기 위해 직렬자비직렬자라는 생성자를 활용한다. 나는 이 글에서 비직렬자와 직렬자라는 문구를 표현하기 위해 (비)직렬자라는 축약어를 사용하겠다.

아파치 SOAP 툴킷에 포함된 기본 (비)직렬자 세트는 세 그룹의 데이터 유형을 처리할 수 있다. : 간단한 유형, 복합 유형특수 유형이 그것이다. 간단한 유형복합 유형이라는 용어는 SOAP 1.1 사양에서 직접 가져온 것이고 문맥상의 의미는 사양 내에서의 의미와 동일하다. 나는 위 두 그룹에 잘 맞지 않는 자바 유형을 가리키기 위해 특수 유형이라는 용어를 만들었다.

간단한 유형

아파치 SOAP은 이 유형들을 텍스트 노드만을 자식으로 가진 접근자로 직접 직렬화한다. 일반적으로, XML 인스턴스는 다음 형식을 가지고 있다.:


<accessor xsi:type="qname">
    <!-- data -->
</accessor>

표 1. 아파치 SOAP에서 지원되는 간단한 유형
Java SOAP 직렬자 비직렬자
String xsd:string built-in* StringDeserializer*
Boolean xsd:boolean built-in* BooleanObjectDeserializer*
boolean xsd:boolean built-in* BooleanDeserializer*
Double xsd:double built-in* DoubleObjectDeserializer*
double xsd:double built-in* DoubleDeserializer*
Long xsd:long built-in* LongObjectDeserializer*
long xsd:long built-in* LongDeserializer*
Float xsd:float built-in* FloatObjectDeserializer*
float xsd:float built-in* FloatDeserializer*
Integer xsd:int built-in* IntObjectDeserializer*
int xsd:int built-in* IntDeserializer*
Short xsd:short built-in* ShortObjectDeserializer*
short xsd:short built-in* ShortDeserializer*
Byte xsd:byte built-in* ByteObjectDeserializer*
byte xsd:byte built-in* ByteDeserializer*
BigDecimal xsd:decimal built-in DecimalDeserializer
GregorianCalendar xsd:timeInstant! CalendarSerializer CalendarSerializer
Date xsd:date DateSerializer DateSerializer
QName xsd:QName QNameSerializer QNameSerializer

  • built-in: org.apache.soap.encoding.SOAPMappingRegistry에서 구현되는 내부 클래스 .
  • * : 아파치 SOAP version 2.1 이후
  • ! : 2001년 1월, W3C XML Schema Datatypes 사양 (참고 자료)이 제안된 권고안 상태로 바뀔 때 timeInstant가 dateTime으로 이름이 바뀌었다. 아파치는 아직 xsd:dateTime을 지원하지 않는다. (참고 자료)

논의를 더 진행하기 전에, 표 1에서 세가지 점을 좀 더 자세히 검토해 보자. 우선 눈치 빠른 독자라면 char가 지원되지 않는다는 점을 알아차렸을 것이다(참고 자료). 다행히 char를 위한 사용자 정의 (비)직렬자를 작성하는 것은 간단한 일이다. 둘째, 업데이트된 BooleanDeserializer는 { "1", "t", "T" } 값은 boolean 문자가 참임을, { "0", "f", "F" }는 거짓임을 표현한다는 것을 인식할 수 있다. 셋째, RPC 호출의 수신자는 기본적으로 원시 코드로 비직렬화할 것이다. 이를 입증하기 위해, SOAP 서버가 다음과 같은 하나의 메소드를 나타낸다고 가정해보자.


echoBoolean( Boolean b ) { .. }

Boolean 매개변수를 가진 echoBoolean을 호출하면 SOAP Fault가 생성된다.:


         Exception while handling service request: 
         com.raverun.bool.Service.echoBool(boolean) -- no signature match

다행히도, 대응하는 wrapper 객체를 반환할 비직렬자 suite (xxxObjectDeserializer)가 있다. 이를 발생시키기 위해 여러분은 이 비직렬자의 기본 비직렬화 행위를 오버라이드해야 한다. 여러분은 이 글의 뒤에서 이것이 어떻게 수행되는지를 볼 것이다.

복합 유형

자바 측면에서 보면, 복합 유형은 구성 요소들을 가진 유형이다. 이 요소들은 순전히 이름(예 : 여러 개의 멤버 속성을 가진 자바 클래스)이나 순서상의 위치 (예 : ArrayVector 같은 List 데이터 구조)에 의해 구별된다. Listing 3 은 복합 유형의 XML 인스턴스를 보여 주고 있다.

Listing 3. java.util.Hashtable에 대한 XML 인스턴스

     <hash xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Map">
     <item>
      <key xsi:type="xsd:string">2</key> 
      <value xsi:type="xsd:string">two</value> 
     </item>
     <item>
      <key xsi:type="xsd:string">1</key> 
      <value xsi:type="xsd:string">one</value> 
     </item>
     </hash>

표 2. 복합 유형에 대한 Section 5 인코딩
Java SOAP 직렬자/비직렬자
Java arrays SOAP-ENC:Array ArraySerializer
java.util.Vector
java.util.Enumeration
{http://xml.apache.org/xml-soap}Vector* VectorSerializer
java.util.Hashtable {http://xml.apache.org/xml-soap}Map HashtableSerializer
java.util.Map {http://xml.apache.org/xml-soap}Map MapSerializer#
Arbitrary JavaBean User-defined Qname BeanSerializer

  • * : 아파치 2.1에서 VectorSerializer 는 SOAP 배열로 직렬화한다. 그러나 2.2.에서는 자체적인 아파치 SOAP 고유 유형으로 소개되었다.
  • # : MapSerializer는 실제로 HashtableSerializer 호출을 위임한다.

표 2에서 볼 수 있듯이, BeanSerializer는 복잡한 자바 클래스들을 임의적으로 전송하는데 사용될 수 있다. 그러나 직렬화된 자바 클래스는 JavaBeans 설계 패턴을 따라야 한다. ; 구체적으로 말해, 직렬화하고자 하는 모든 속성에 대해 매개 변수가 없는 public 생성자와 getter/setter 메소드를 가지고 있어야 한다. 여러분의 getter/setter 메소드가 JavaBeans 네이밍 관습을 만족하지 않는 상황에서는 BeanInfo 클래스를 통해 BeanSerializer로 이들을 공개할 수 있다. 예를 들어, Foo 클래스를 직렬화해야 할 경우 java.beans.BeanInfo 인터페이스를 구현하는 FooBeanInfo라는 클래스를 생성한다. 예제로 Listing 4를 보자.

Listing 4. FooBeanInfo 클래스

import java.lang.reflect.*; 
import java.beans.*;

/*
 * Foo has a single String property by the name of "s".
 * And its setter and getter methods are called "_setS" and "_getS"
 * respectively, thus violating the JavaBean spec. Passing Foo to BeanSerializer
 * will cause this exception to be thrown:
 *        Error: Unable to retrieve PropertyDescriptor for property 's' 
 *               of class "Foo".
 */
public class FooBeanInfo extends SimpleBeanInfo{

  private final static Class c = Foo.class;

  public PropertyDescriptor[] getPropertyDescriptors()
  {
    try{
      Class[] sp = new Class[] {String.class};
      Method s_setter = c.getMethod( "_setS", sp );
      Method s_getter = c.getMethod( "_getS", null );
      PropertyDescriptor spd = new PropertyDescriptor("s", s_getter, s_setter);      
      PropertyDescriptor[] list = { spd };
      return list;
    }catch (IntrospectionException iexErr){
      throw new Error(iexErr.toString());
    }catch (NoSuchMethodException nsme){
      throw new Error(nsme.toString());
    }
  }
}

특수 유형

위의 두 카테고리에 잘 맞지 않는 많은 자바 유형들이 있다.; SOAP은 이 유형들도 잘 처리할 수 있다.

Null and void

아파치 SOAP은 xsi:null 속성을 추가함으로써 null 값을 가진 객체 참조를 "참"으로 직렬화 것이다. 이것의 기본 행위는 다음과 같은 xsi:null 속성의 다른 가능한 변형들은 무시한다.:

Listing 5는 null 값을 가지고 있는 직렬화된 객체 참조의 예이다.

Listing 5. 희소 벡터를 가진 SOAP 표현

<myvec xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Vector">
<item xsi:type="xsd:string">Hello World</item>
<item xsi:type="xsd:anyType" xsi:null="true"/>
</myvec>

아파치 SOAP 2.1에서 null 멤버를 가진 벡터를 전송하면 null 참조를 비직렬화할 비직렬자가 존재하지 않기 때문에 SOAPException이 발생한다.

UrTypeDeserializer는 이 때문에 도입된다. VectorSerializerXML 스키마의 내장 데이터 유형 계층 내의 루트 클래스인 xsd:anyType에 null 참조를 매핑한다. (2001 XML 스키마 권고안에서 xsd:ur-type은 누락되었다.)

문자열 매개변수를 처리할 때 접근자가 xsi:null 속성을 가지지 않은 빈 element인 경우 아파치 SOAP은 null 객체 참조를 비직렬화하지 않는다. 예를 들어, 원격 메소드가 다음 String 매개변수를 기대할 경우 :


public void eat(String s) //1

그리고 여러분이 이 요청이 다음과 같이 웹 서비스로 전송된다고 가정할 경우:


<ns1:eat>                       
<s xsi:type="xsd:string"/>
</ns1:eat>

이것은 빈 String으로 비직렬화할 것이다.

바이너리 데이터
긴 구조의 바이너리 데이터를 전송해야 할 경우 다음 세 옵션이 있다.:

  • 바이트 배열
  • 16진 문자열
  • MIME 첨부

표 3. 데이터 전송하기
Java SOAP 직렬자/비직렬자
바이트배열 SOAP-ENC:base64# Base64Serializer
16진 문자열 xsd:hexBinary HexDeserializer
javax.activation.DataSource xsd:anyType MimePartSerializer
javax.activation.DataHandler xsd:anyType MimePartSerializer

  • #: 최신 SOAPMappingRegistry 버전은 비직렬화 과정 동안 {http://www.w3.org/2001/XMLSchema}base64Binary - SOAP-ENC:base64의 최상위 유형 -을 지원한다.

wrapper 클래스인 org.apache.soap.encoding.Hex는 16진 문자열을 캡슐화하는데 사용된다. 이것은 (비)직렬화되는 클래스이다. wrapper 클래스는 a에서 f까지 16진 값에 대한 소문자와 대문자 모두를 수용할 것이다. 16진 문자열이 문자를 짝수로 가지도록 하기만 하면 된다.

아파치 SOAP은 MIME/multipart 관련 메시지에 SOAP 봉투를 포함시키도록 지원한다. 바이너리 데이터는 MIME 첨부 문서로 전달될 수 있다. 이것은 SwA (SOAP Messages with Attachments) 사양을 따른다. (참고 자료)

Literal XML
여러분이 XML 부분을 문자열로 전송한다면 내장된 문자열 직렬자는 element 컨텐트로써 부적당한 모든 문자들을 사전 정의된 문자열 엔터티로 바꿀 것이다. -- 예를 들어, <는 &lt로 바뀔 것이다. SOAP RPC 구조 내에 포함된 literal XML을 전송하려면 두 가지 옵션이 있다. 우선 XML 문자열을 CDATA 섹션 내에 둘 수 있다. 그러나 이 방식은 잘못 만들어진 XML도 함께 보내게 될 것이다. 두번째 방법은 여러분의 XML 부분을 DOM에 로딩하여 문서 element를 매개변수로 보내는 것이다. 아파치 SOAP은 XMLParameterSerializer를 통해 org.w3c.dom.Element 인스턴스를 직렬화할 것이다.

유형 매핑 패턴

아파치 SOAP으로 SOAP 클라이언트와 서버를 프로그래밍할 때 여러분은 표 4에 나와 있는 메시지를 가진 염려스러운 java.lang.IllegalArgumentException을 만날 수 있다.

표 4. 일반적인 유형의 mapper 에러 메시지
메시지
1 No Serializer found to serialize a 'xxx' using encoding style 'yyy'
2 No Deserializer found to deserialize a ':Result' using encoding style 'yyy'
3 No mapping found for 'xxx' using encoding style 'yyy'

아파치 SOAP에서의 직렬화와 비직렬화 메커니즘은 레지스트리에 정의된 유형 매핑이 사용가능한지에 의존한다. 유형 매핑은 자바 클래스 (혹은 원시 코드)를 XML로 어떻게 변환하는지, 혹은 그 역을 정의한다. 레지스트리는 org.apache.soap.encoding.SOAPMappingRegistry 클래스의 인스턴스이다. 기본적으로 SOAP 클라이언트와 SOAP 서버 모두 동일한 유형 매핑 세트를 상속할 것이다.

그림 1. 문맥 내에서의 직렬화/비직렬화
Serialization/deserialization in context

아래의 논의에서, 나는 SOAP 메시지 처리의 방향을 표시하기 위해 inbound와 outbound라는 용어를 사용할 것이다. 예를 들어, SOAP 서버의 문맥 내에서 직렬화는 outbound 메시지를 생성하는 방법을 가리킨다. 이 간단한 모델은 우리의 논의를 방해하지 않으면서 중개 SOAP 서버를 무시한다. 그림 1은 논의되고 있는 아키텍처를 나타내고 있다.

유형 매핑 레지스트리와 상호작용하기 위한 프로그램적인 구문을 설명하기 전에 레지스트리의 내부 구현에 대한 몇 가지 개념을 알면 도움이 될 것이다. 개발자가 상호작용하는 레지스트리는 실제로 4개의 내부 Hashtables (아래의 목록 참조)을 가지고 있다.

  1. 직렬자용 레지스트리
  2. 비직렬자용 레지스트리
  3. Java2XML용 레지스트리
  4. XML2Java용 레지스트리

직렬화와 비직렬화는 대칭적인 기능들이다.; 따라서 나는 직렬화 유형 매핑이 어떻게 저장되는지만 설명할 것이고, 여러분이 내 설명에서 비직렬화의 경우를 추정할 수 있기 바란다. 직렬화 유형 매핑은 위 목록에서 A,C, D 해시테이블에 있는 엔트리들에 의해 표현된다. Foo. 클래스 객체를 SOAP 유형 "{http://someuri}foo"으로 직렬화시키는 FooSerializer 직렬자를 예로 들어 보자. 여러분이 이 유형 매핑에 대한 유일한 키를 나타내기 위해 문자열 k를 고려하고 있다면, 이들은 직렬화를 위해 만들어진 매핑이다.

  • Hashtable A: kFooSerializer.class에 매핑된다.
  • Hashtable C: k{http://someuri}foo에 매핑된다.

해시테이블 A와 C 모두 자바 런타임 유형과 encodingStyleURI를 결합하여 생성된 공유된 문자열이 입력된다. 해시테이블 A는 org.apache.soap.util.xml.Serializer 인터페이스를 구현하는 자바 객체 집합을 유지하는 반면 해시테이블 C는 QNames 집합을 관리한다. 직렬화 절차 동안 레지스트리는 두 개의 API 호출을 받는다.


  Serializer querySerializer( javaType, encodingStyleURI )
  QName queryElementType (javaType, encodingStyleURI )

아파치 SOAP은 Foo 인스턴스를 직렬화하라는 명령을 받으면 querySerializer 메소드를 호출하고 FooSerializer를 다시 가져온다. 그러면 FooSerializer가 객체를 배열하기 위해 호출된다.; 이때 xsi:type 속성 값을 검색하기 위해 queryElementType을 호출할 것이다.

querySerializer가 Serializer 인스턴스를 반환하는데 실패하면 여러분은 표 4에 있는 에러 메시지를 얻게 될 것이다. 반면 queryElementType이 일치되는 QName을 반환하지 않으면 에러 메시지 3이 반환된다.

SOAP 클라이언트에 유형 매핑 정의하기

여러분은 SOAPMappingRegistry 인스턴스에 mapTypes() 메소드를 호출함으로써 여러분의 특수 유형을 처리하도록 레지스트리를 확장할 수 있다. 이 메소드 서명은 다음과 같다.:


  mapTypes( String, QName, Class, Serializer, Deserializer );

해시테이블 C와 D로 동시에 매핑이 등록되도록 하고 싶으면 QNameClass 매개변수가 필수적이다. Listing 6은 아파치 SOAP의 기본 유형 매핑들의 예이다.

Listing 6. 예제 클라이언트 maptypes

HashtableSerializer hashtableSer = new HashtableSerializer();
mapTypes("http://schemas.xmlsoap.org/soap/encoding/", 
         new QName("http://xml.apache.org/xml-soap", "Map"),
         Hashtable.class, 
         hashtableSer, 
         hashtableSer);

mapTypes("http://schemas.xmlsoap.org/soap/encoding/", 
         new QName("http://schemas.xmlsoap.org/soap/encoding/", "Array"),
         null,
         null,
         new ArrayDeserializer());

mapTypes( encStyleURI, Qname, Foo.class, null, null );

Listing 6의 해시테이블 매핑은 inbound와 outbound 처리가 모두 등록되는 대칭형 매핑의 예이다. 두번째 매핑은 비대칭형 매핑인데, ArrayDeserializer만 해시테이블 B에 등록한다. 배열 직렬화는 SOAPMappingRegistry.querySerializer() 내에서 발생하는데, 자바 유형이 배열인지를 확인하기 위해 introspection을 사용하고 ArraySerializer의 새로운 인스턴스를 반환한다. 마지막 매핑은 순수한 연결이다.

SOAP 서버에 유형 매핑 정의하기

서버 측에서는 유형 매핑이 배치 기술자 (deployment descriptor)라는 XML 파일로 정의된다. 아파치 SOAP은 실제로 배치 기술자의 내용에서 SOAPMappingRegistry 인스턴스를 구축한다. XML 문법을 위한 스키마는 이 글과 함께 제공되는 소스 배포판에서 사용할 수 있다 (참고 자료). Listing 7에서 이 스키마의 일부를 볼 수 있는데, map element의 속성을 나타내고 있다.; 그 속성은 SOAPMappingRegistrymapTypes 메소드의 매개변수를 미러링한다. 차이점은 qname이 map에 필수적이라는 것 뿐이다.

Listing 7. 아파치 SOAP 배치 기술자의 map elememnt에 대한 스키마의 일부분

    <complexType name="mapType">
      <attribute name="encodingStyle" type="xsd:anyURI"/>
      <attribute name="qname" type="xsd:string"/>
      <attribute name="javaType" type="xsd:string" use="optional"/>
      <attribute name="java2XMLClassName" type="xsd:string" use="optional"/>
      <attribute name="xml2JavaClassName" type="xsd:string" use="optional"/>
    </complexType>

컴파일된 사용자 정의 매핑

유형 매핑들을 SOAPMappingRegistry의 하위 클래스에 통합하면 유형 매핑을 수작업으로 선언하는 지겨운 작업을 뛰어넘을 수 있다. 이 하위 클래스는 클라이언트와 서버 모두에서 사용될 수 있다. Listing 8은 자체적인 배열 (비)직렬자를 제공하는 SOAPMappingRegistry 하위 클래스에 대한 코드 샘플을 포함하고 있다.

Listing 8. public 클래스 MySmr을 하위 클래스로 통합하면 SOAPMappingRegistry가 확장됨

  public MySmr()
  { 
    super(); 
    registerMyMappings();
  }

  public MySmr(String schemaURI)
  { 
    super(schemaURI); 
    registerMyMappings();
  }

  private void registerMyMappings()
  {
    MyArraySerializer arraySer = new MyArraySerializer();
    mapTypes(Constants.NS_URI_SOAP_ENC,
             new QName(Constants.NS_URI_SOAP_ENC, "Array"), 
             null,
             arraySer,
         arraySer);
  }
}

호출자 측에서 이러한 사용자 정의 SOAPMappingRegistry를 사용하려면 Call 객체의 setSOAPMappingRegistry() 메소드에 대한 매개변수로 이를 전달한다. 제공자 측에서는 여러분의 모든 개별적인 map element를 삭제하고 대신 Listing 9와 같이 배치 기술자를 수정한다.

Listing 9. 배치 기술자 변경

<isd:service ...>
  // other elements here
  <isd:mappings defaultRegistryClass="com.raverun.array.MySmr" />
</isd:service>

아파치 SOAP이 상이한 스키마 이름공간들을 처리하는 방법

Listing 9에서 여러분은 SOAPMappingRegistry 클래스가 String 매개변수를 취하는 오버로드된 생성자를 가지고 있음을 알아차렸을 것이다. 이 매개변수는 실제로 W3C의 XML 스키마 언어의 특정 버전을 나타내는 URI이다. 아파치 SOAP이 수용하는 버전은 org.apache.soap.Constants 내에 최종적으로 사전 정의되어 있다.

  • Constants.NS_URI_1999_SCHEMA_XSD = http://www.w3.org/1999/XMLSchema
  • Constants.NS_URI_2000_SCHEMA_XSD = http://www.w3.org/2000/10/XMLSchema
  • Constants.NS_URI_2001_SCHEMA_XSD = http://www.w3.org/2001/XMLSchema

여러분이 매개 변수가 없는 생성자로 SOAPMappingRegistry를 초기화하면 아파치 SOAP은 기본적으로 1999 이름공간에 유형을 직렬화할 것이다. 다중 부여된 생성자를 호출하면 실제로 xsd 이름공간만 오바라이드한다.; SOAP 봉투에 선언된 xsi 이름공간은 바뀌지 않은 채로 있다. 이것은 SOAP 봉투가 자신의 이름공간 접두사 매핑을 Constants.NS_URI_CURRENT_SCHEMA_XSDConstants.NS_URI_CURRENT_SCHEMA_XSI에서 직접 가져오고, 이것이 1999 이름공간을 가리키기 때문이다. 이러한 결점은 최신 CVS 소스 릴리즈 (참고 자료)에서 고쳐졌다. 그러나 비직렬화 관점에서 보면 아파치 SOAP은 나열된 세 버전 중 어디에서의 스키마 유형도 잘 처리할 수 있다.

인코딩 스타일과 유형 매핑

유형 매핑 레지스트리의 특성에 대한 우리의 지식에 의하면 encodingStyleURI는 직렬화와 비직렬화가 어떻게 진행되어야 하는지 결정하는데 중요한 역할을 한다. 이번 섹션에서 나는 encodingStyleURI의 사용에 영향을 미치는 몇 가지 프로그래밍 사항들을 강조하겠다.

우선, SOAP 1.1 사양은 "SOAP 메시지에 대한 기본 인코딩이 정의되지 있지 않다"라고 분명하게 언급하고 있다. 따라서 아파치 SOAP에서 기본 encodingStyleURI는 null이다. 여러분이 Call 객체나 각 매개변수의 선언 내의 값에 이를 초기화하지 않았다면 SOAPMappingRegistrysetDefaultEncodingStyle 메소드를 호출함으로써 이를 수행할 수 있다. 이것은 로컬 범위의 encodingStyleURI 속성을 여러분의 모든 매개변수 접근자에 추가하는 것과 같다.

둘째, SOAP 응답과 SOAP 요청의 encodingStyle이 달라야 하는 시나리오를 고려해 보자. 논의를 수월하게 하기 위해 Listing 10countKids() 메소드를 검토해 보자. 이 메소드의 inbound encodingStyles과 outbound encodingStyle은 각각 literal XML과 Section 5이다.

Listing 10. 예제 SOAP 서버의 코드 부분

public int countKids( Element el ){
  return( DOMUtils.countKids(el, Node.ELEMENT_NODE) );
}

여러분이 Listing 11의 코드로 countKids()를 호출하면 SOAP 에러가 다음 메시지와 함께 반환될 것이다. : java.lang.IllegalArgumentException: I only know how to serialize an 'org.w3c.dom.Element'

Listing 11. RPC 호출을 위한 코드 부분

Call call = new Call();
Vector params = new Vector();
params.addElement( new Parameter("xmlfile", 
                                 Element.class,
                                 e,
                                 Constants.NS_URI_LITERAL_XML) ); 
call.setParams( params );
call.invoke ( url, "" );

왜 이런 에러가 발생했는지 이해하려면, 아파치 SOAP (SOAP 서버 상의)이 outbound 메시지에 어떤 encodingStyle을 사용할지 결정하기 위해 사용하는 알고리즘을 알아야 한다. :

  1. 사용가능한 경우 메소드 wrapper 접근자의 encodingStyle 속성을 사용한다.
  2. 이 속성이 없는 경우 첫번째 매개변수에서 encodingStyle을 추론한다.

따라서, 여러분의 메소드가 여러 개의 매개변수를 받아들일 경우, 여러분이 CallencodingStyle 속성을 명확하게 설정하지 않았다면 여러분은 그 순서를 재구성하고 싶을 수 있다. 완벽하게 안전하게 하고 싶다면 Call 객체내encodingStyle을 원격 메소드의 반환 유형에 대응되도록 설정한다.

SOAP 메시지 내의 encodingStyle 혼합하기

무엇보다도 우선적으로, SOAP 사양은 매개변수 내의 encodingStyl0들을 홉합하도록 해준다.; encodingStyle 속성의 범위는 이름공간 선언과 유사하게 행동한다. 그러한 혼합의 유용한 경우가 SGV 부분의 배열이다. 언뜻 보면 Section 5를 사용하여 배열을 직렬화하고 literal XML을 사용하여 SGV 멤버를 직렬화하는 것이 그럴듯해 보일 수 있다. 불행히도, 모든 Section 5 직렬자는 Section 5 encodingStyles 아래에 등록된 유형과만 작동하기 때문에 이러한 작업은 발생하지 않는다. 다시 말하면, Section 5로 인코딩된 XML 스트림 내의 모든 것은 변경이 불가능하고 다른 encodingStyle을 사용하여 인코딩된 XML 부분을 포함할 수 없다. 또한 최근의 버그 수정판 (참고 자료)은 배열, 해시 테이블, 벡터와 같은 복합 데이터 구조 내의 값이 Section 5 외의 encodingStyle은 가질 수 없음을 확실하게 하였다. .

사소한 유형 매핑 문제들

우리는 SOAP 유형 매핑 작업시 여러분이 만날 수 있는 몇 가지 문제들과 특이 사항들을 언급하면서 이 글을 결론짓도록 하겠다.

비직렬화 과정 동안 xsi:type 완화하기
마이크로소프트사의 SOAP 툴킷에 의해 생성된 inbound 메시지를 처리할 때 여러분은 다음과 같은 에러를 만날 것이다.:


No Deserializer found to deserialize a ':Result' using encoding style 'yyy'

이 고전적인 상호 운용성 문제는 접근자에 xsi:type 속성이 없기 때문에 발생한다. 아파치 SOAP 2.1 출시판에 구현된 솔루션은 다음 규칙에 따라 xsi:type 값을 동적으로 생성하는 것이다.: 매개변수의 접근자가 어떤 이름 공간의 범위 내에도 있지 않은 경우 다음 QName을 사용한다.:


{""}/X

여기에서 X 는 접근자의 tagName을, ""은 빈 URI를 가리킨다. 빈 URI 자리에 비직렬화되고 있는 접근자(존재하는 경우)의 이름공간 URI를 사용한다. 그렇게 하면, 이 임시 QNames을 (비)직렬자와 매치시키기 위한 유형 매핑이 생성된다. Listing 12는 클라이언트와 서버 모두에 대한 샘플 유형 매핑을 보여주고 있다.

Listing 12. RPC 호출을 위한 코드 부분

    [Client]
    
    SOAPMappingRegistry smr = new SOAPMappingRegistry ();
    StringDeserializer sd = new StringDeserializer ();
    smr.mapTypes (Constants.NS_URI_SOAP_ENC,
                  new QName ("", "Result"), null, null, sd);

    [Server]
    
    <isd:mappings>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:Book"
             javaType="com.raverun.Book"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:price"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.FloatObjectDeserializer"/>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:isbn"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.StringDeserializer"/>
    </isd:mappings>

유형 매핑은 다음 관습을 따라야 한다.:

  • 원시 유형들과 이들의 wrapper 객체에 대해서는 (예 : intInteger) 유형 매핑에 javaType을 지정하지 않는다.
  • 복합 유형에 대해, 여러분의 비직렬자가 임무를 수행할 때 javaType 을 알아야 할 경우 (예 : org.apache.soap.encoding.soapenc.BeanSerializer) 여러분은 javaType을 지정할 수 있다.

이 행위에 대한 상세 사항은 Sanjiva Weeraweena가 soap-dev 메일링 리스트에 올린 게시물들과 Jim Senll의 글들에서 모을 수 있다. (참고 자료)

복수 참조 유형

복수 참조 유형은 새로운 유형 그룹은 아니지만, 간단한 유형과 복합 유형의 한 면으로 고려되어야 한다. 이들은 매개변수들간, 특히 DCE 유형 [ptr] 의미론을 나타내는 매개변수들간의 동일성을 유지하는데 유용하다. 아파치 SOAP은 복수 참조 유형을 비직렬화할 수 있지만 이들을 직렬화하지는 않는다.

복수 참조 유형은 두 부분으로 직렬화된다. : 매개변수 접근자 (빈 element)와 값 접근자가 그것이다. Listing 13에서 varString element는 매개변수 접근자이고 greeting은 값 접근자이다. 값 접근자는 메소드 wrapper 접근자인 ns1:echoString과 직계 동기인 element임에 주의한다. 아파치 SOAP은 직렬화 그래프의 루트 (메소드 wrapper 접근자)가 SOAP-ENV:Body의 직계 자식이라고 가정하기 때문에 이 점은 중요하다. 값 접근자가 먼저 와야 할 경우 여러분은 Server.BadTargetObjectURI 에러를 얻을 것이다. SOAP 사양은 한 element가 직렬화 루트가 아니라는 것을 가리키기 위해 SOAP-ENC:root 속성을 제공하지만, 아파치 SOAP이 이를 인식하지 않는다.

Listing 13. 복수 참조 매개변수의 예제


<SOAP-ENV:Body>
  <ns1:echoString 
    xmlns:ns1="http://soapinterop.org" 
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <varString href="#String-0" />
  </ns1:echoString>
  <greeting 
    xsi:type="xsd:string" 
    id="String-0">Hello World</greeting>
</SOAP-ENV:Body>

XMI 인코딩
XMI (XML Metadata Interchange)는 애플리케이션간 UML 모델을 공유하기 위한 OMG 표준이다. 가장 일반적으로 사용되는 곳이 UML 모델을 XML 파일로 엑스포트하고 XML 파일을 UML 모델로 임포트하는 UML 모델링이다. IBM XMI 프레임워크 내의 Java Object Bridge (JOB)는 자바 객체가 XMI 포맷으로 (비)직렬화하도록 해 준다. 이리하여 XMI는 Section 5 인코딩에 대한 대안으로 고려될 수 있지만, 자주 사용되지는 않는다.

결론 및 예측

이 글에서 나는 자바 유형을 (비)직렬화하기 위한 아파치 SOAP의 즉시 이용가능한 대부분의 API 지원을 검토하였다. 나는 또한 다른 자바 기반 및 비 자바 기반 SOAP 툴킷과의 상호운용을 방해할 수 있는 몇 가지 특이 사항들을 다루었다.

다음 편에서 나는 여러분 자신의 (비)직렬자를 작성하도록 안내해 줄 요리책을 소개하겠다. 여러분은 또한 스키마 언어를 사용해 비 Section 5 인코딩과 작업하는 방법을 보게 될 것이다.

참고 자료

  • Apache SOAP : 자바 프로그래밍 언어를 위한 오픈 소스 SOAP 툴킷
  • Apache Axis : 아파치 SOAP의 뒤를 잇는 차세대 툴킷
  • Brendan Macmillan의 Java Serialization to XML (JSX) 툴킷은 유용한 몇 가지 툴을 가지고 있다.
  • Sun의 XML Binding (JAXB)을 위한 자바 아키텍처.
  • Castor : 자바 프로그래밍 언어를 위한 오픈 소스 데이터 바인딩 프레임워크
  • Schema2Java : Creative Science Systems의 상용 XML 데이터 바인딩 제품 .
  • 아파치 SOAP의 배치 기술자에 대한 xsd 스키마
  • 이 시리즈와 함께 제공되는 샘플 코드인 PurchaseOrder.zipPurchaseOrder.tar.gz를 다운로드받는다. 나는 다음 편에서 이 코드들에 대해 상세히 설명할 것이다.; 그러나 이 패키지는 여러분이 지금 검토하기를 원할 수 있는 XML 문법에 대한 스키마를 포함하고 있다.
  • TXMI (XML Metadata Interchange)에 대한 상세 사항은 IBM XMI 프레임워크를 참조한다.
  • SOAP 1.1 사양은 W3C Technical Note 형태로 사용할 수 있다.
  • Sanjiva Weerawarana의 게시문: 아파치 SOAP에서 xsi:type 요건 완화하기에 관해 설명하고 있다.
  • "웹 서비스 내부, Part 3" (James Snell, developerWorks, May 2001)는 Apache SOAP과 다른 툴킷과의 상호운용성을 다루고 있다.
  • 여러분은 SOAP과 자바 기술에 관한 IBM workshop에 참여할 수 있다.
  • "첨부문서를 가진 SOAP 메시지" (W3C note) : SOAP이 MIME에 임베디드되는 방식 설명
  • W3C XML 스키마, Part 1 : XML 스키마의 핵심 개념 및 구문
  • W3C XML 스키마, Part 2 : XML 스키마에서 지원되는 데이터 유형
  • 아파치 SOAP 버그 리포트 #2865 : char 원시 유형을 지원하지 않는 것에 대한 설명
  • 아파치 SOAP 버그 리포트 #3000 : timeInstant 버그 설명
  • 아파치 SOAP 버그 리포트 #2388 : 구형 XML 스키마 이름공간 URI 사용에 대한 설명
  • 아파치 SOAP 버그 리포트 #2470 : SOAP과 literal XML 인코딩 스타일을 혼합하는 HashtableSerializer 문제에 대한 설명

필자 소개
Gavin BongGavin Bong은 말레이시아 Kuala Lumpur의 자바 개발자이다. 그의 흥미 분야는 service-oriented architecture와 무선 자바이다. 그는 그림 1 작성에 도움을 준 Pee Eng에게 감사를 전하고 싶어한다.



이 기사에 대하여 어떻게 생각하십니까?

정말 좋다 (5) 좋다 (4) 그저그렇다 (3) 수정보완이 필요하다(2) 형편없다 (1)

  회사소개  |  개인보호정책  |  법률  |  문의