제26장 JAX-RPC 웹 서비스 데이터 타입

내용 목차

26.1. 개요
26.2. Java와 XML 타입 매핑
26.2.1. 내장 타입 매핑
26.2.2. 배열
26.2.3. 사용자 정의 타입 : JAX-RPC Value Type
26.3. JAX-RPC Value 타입의 사용
26.3.1. JAX-RPC Value 타입을 사용하는 웹 서비스 생성
26.3.2. JAX-RPC Value 타입을 사용하는 웹 서비스 클라이언트 생성
26.4. Holder 클래스
26.4.1. 내장 Holder 클래스
26.4.2. 사용자 정의 타입을 위한 Holder 클래스 작성
26.5. Exception과 SOAP Fault
26.6. MIME 타입을 DataHandler 타입으로 매핑
26.6.1. Wsdl2java에서 dataHandlerOnly 옵션 사용
26.7. Doc/Literal에서 데이터 바인딩을 사용하지 않기
26.7.1. Wsdl2java에서 noDataBinding 옵션 사용

본 장에서는 JAX-RPC 웹 서비스에 관한 여러 가지 데이터 타입 문제에 관해 설명한다.

표준 Java/XML 데이터 타입 매핑(type mapping)과 사용자 정의 클래스들에서 웹 서비스 파라미터로 사용할 JAX-RPC의 value 타입에 대해서 설명한다. 그 다음으로 출력 또는 입/출력 파라미터들을 위한 JAX-RPC의 Holder 클래스를 설명하고, 마지막으로 에러 정보를 웹 서비스 클라이언트로 어떻게 전송하는지를 설명한다.

본 절에서는 웹 서비스와 웹 서비스 클라이언트에서 사용하는 데이터 타입에 관한 이슈에 대해서 설명한다.

다음은 웹 서비스 데이터 타입의 특징이다.

지금까지 하나의 타입만(String만)을 파라미터와 반환값으로 사용하는 웹 서비스 예제를 보았다. JEUS 웹 서비스는 Java 타입을 XML 또는 WSDL 정의로 매핑한다. 예를 들어 JEUS 웹 서비스는 java.lang.String 클래스를 XML xsd:string 데이터 타입으로 매핑한다.

본 절에서는 JEUS 웹 서비스에서 지원하는 데이터 타입들의 종류와 어떤 데이터 타입이 JEUS 웹 서비스에서 사용되기 위해 필요한 요구 사항인지에 대해서 설명한다.

참고

애플리케이션 개발자들은 Java와 XML 타입의 매핑 과정에 대해서 자세히 알아야 할 필요는 없다. 그러나 모든 Java 클래스가 파라미터와 반환 타입으로 사용될 수 있는 것은 아님에 주의한다.

다음은 Java와 XML의 데이터 형과 JEUS 웹 서비스의 내장 타입 매핑(Built-in Type Mapping)을 정리한 표이다.

참고

1. 'xsd' 접두어는 XML namespace URI(http://www.w3.org/2001/XMLSchema)를 나타낸다.

2. 'SOAP-ENC' 접두어는 XML namespace URI(http://schemas.xmlsoap.org/soap/encoding)를 나타낸다.

3. 만약 WSDL에서 어떤 객체가 null이 될 수 있다고 정의되어 있다면 서비스 호출자는 xsd:nil을 데이터로 보내거나 받을 때 사용할 수 있다. 그리고 Java primitive 타입은 Wrapper 클래스로 교체된다. 위의 표에서 Java 타입 뒤에 붙은 "*"는 이것을 나타낸다.

DII 클라이언트에서는 어떤 타입을 지정할 때, XML의 QName을 사용할 수 있다.

String NS_XSD = “http://www.w3.org/2001/XMLSchema”;
String XSD_DATETIME = new QName(NS_XSD, “dateTime”);
call.addParameter(“arg1”, XSD_DATETIME, PARAM_MODE_IN);

“26.2.3. 사용자 정의 타입 : JAX-RPC Value Type”에서 설명한 규칙들을 따르는 사용자 정의형들은 웹 서비스의 파라미터나 반환 타입으로써 사용이 가능하다. 이런 종류의 사용자 정의 타입은 JAX-RPC Value 타입이라고 한다.

본 절에서는 JAX-RPC Value 타입을 사용하는 예제를 설명한다. CalcService는 2개의 숫자와 하나의 연산자를 받아서 결과를 숫자로 넘겨주는 예제이다. 그리고 CalcService를 위한 웹 서비스 클라이언트를 작성한다.

다음은 CalcService 소스 코드인 Calculator.java 코드이다.


calc() 메소드는 CalcData형을 인자로 받고, 에러가 발생하면 –9999.0을 반환한다. 이후에 설명하는 예제에서는 좀 더 확장된 에러 처리방법을 설명할 것이다.

다음은 CalcData.java의 소스 코드이다.


CalcData 클래스는 JAX-RPC Value Type 요구 사항을 따르고 있다. 이것은 CalcData의 인스턴스는 값으로서 전달될 수 있음을 의미한다.

다음은 Service Endpoint Interface 파일 CalculatorIF.java 코드이다.


이 파일들을 컴파일하기 위해서 다음과 같이 명령어를 수행한다.

$ ant compile

위 명령은 결과 클래스 파일들을 build 디렉터리 아래에 옮겨놓을 것이다.

배치 가능한 EAR 파일을 생성하기 위해서는 다음 명령을 수행한다.

ant wsear

웹 서비스 모듈을 배치하면 다음과 같은 주소로 서비스에 접근할 수 있다.

http://localhost:8088/Calculator1Service/Calculator1Service?wsdl

다음의 명령을 수행하면 CalcService 웹 서비스를 위한 프록시 클라이언트를 생성한다.

ant wsdl2java

생성된 Stub 코드의 패키지 이름은 com.test.calc로 가정한다.

다음은 클라이언트 프로그램의 소스 코드이다.


클라이언트 코드 구현이 완료되면 다음 명령을 통해 클라이언트 코드와 Stub 코드를 컴파일한다.

$ ant build

클라이언트를 실행하기 위해서 다음과 같이 Ant Task를 실행한다.

$ ant runclient

성공하면 다음과 같은 결과가 출력된다.

2.0

웹 서비스가 여러 개의 값을 반환하기를 원한다면 JAX-RPC Value 타입을 정의하거나 출력 또는 입/출력 파라미터를 하나 이상 지정해야 한다. Holder 클래스는 출력 또는 입/출력 파라미터로 사용되는 Helper 클래스이다.

JEUS 웹 서비스는 단순 데이터 타입의 JAX-RPC Holder 클래스들을 제공한다. JAX-RPC가 지원하는 표준 Holder 클래스들은 다음과 같다.

각 Holder 클래스들이 가지고 있는 값을 액세스하기 위해 value 필드를 사용한다.

다음은 Caculator.java를 수정한 소스이다.


소스 코드는 내장 Holder 클래스인 javax.xml.rpc.holders.DoubleHolder를 import하였다.

import javax.xml.rpc.holders.DoubleHolder;

calc() 메소드의 signature 또한 수정되었다. 리턴 타입은 void이고, 두 번째 파라미터로 Holder 객체를 받는다.

public void calc(CalcData data, DoubleHolder result){
. . .
}

Holder 객체가 가진 객체 값을 접근하기 위해서는 Holder 클래스의 value 필드를 이용한다.

result.value = ret;

웹 서비스를 위한 웹 서비스 클라이언트를 생성하기 전에 이 웹 서비스를 패키지로 생성하고 배치해야 한다. 또한 클라이언트 프로그램 CalcClient.java도 수정되어야 한다.

다음은 클라이언트 소스 코드 CalcClient.java의 run() 메소드의 내용이다.


주의

CalcClient.java를 컴파일하기 전에 프록시 소스 코드를 다시 생성하는 것을 잊지 않도록 주의한다.

다음은 사용자가 작성한 클래스의 Holder 클래스를 작성하는 과정이다.

  1. javax.xml.rpc.holders.Holder 인터페이스를 구현한다.

  2. 사용자가 작성한 클래스를 TypeHolder라고 명명한다.

    Type은 Holder 객체가 가지게 될 클래스의 이름이다. 예를 들어 CalcData 클래스를 위한 Holder를 작성하기 위해서는 Holder 클래스의 이름은 CalcDataHolder가 된다.

  3. 작성한 Holder 클래스를 public으로 선언한다.

  4. value라는 이름을 가진 public 필드를 생성한다. 이 데이터 타입은 Holder 클래스의 타입과 같다.

  5. value 필드를 기본값으로 초기화하는 파라미터 없는 default 생성자를 생성한다.

  6. 파라미터 값으로 value 필드를 설정하는 생성자를 생성한다.

JAX-RPC Value 타입의 Holder 클래스를 사용하는 방법을 설명하기 위해서 CalcService 예제를 수정한다. 다음 예제에 클래스 CalcData는 계산 결과값을 멤버로 포함한다. private 변수인 result와 getter/setter 메소드를 멤버로 추가하였다.


CalcData의 Holder 클래스는 CalcDataHolder이다. CalcDataHolder 클래스는 데이터 타입이 CalcData인 public의 value 필드와 value 필드를 초기화하는 2개의 생성자를 가지고 있다.


Holder 클래스를 사용하는 Calculator.java 소스 코드는 다음과 같다. calc() 메소드는 데이터 타입이 CalcDataHolder인 파라미터를 지정하고 있다. Holder 객체가 가지고 있는 객체를 접근하기 위해 public의 value 필드를 사용하고 있다.

package calc;

public class Calculator implements CalculatorIF {
    public Calculator() { }
    
    public void calc(CalcDataHolder calcData) {
        CalcData data = calcData.value;
        String op = data.getOp();
        double num1 = data.getNum1();
        double num2 = data.getNum2();
        double ret = -9999.0;

        if (op.equals("plus")) {
            ret = num1 + num2;
        } else if (op.equals("minus")) {
            ret = num1 - num2;
        } else if (op.equals("mult")) {
            ret = num1 * num2;
        } else if (op.equals("div")) {
            if (num2 != 0)
                ret = num1 / num2;
        }
        data.setResult(ret);
        
        // The following line is not necessary in this case.
        // But we will use it to show the usage of holder class.
        calcData.value = data;
    }
}

다음은 클라이언트 소스 코드의 내용이다. Ant wsdl2java 명령어 또한 WSDL 문서로부터 클라이언트를 위한 Holder 클래스들을 생성한다. Ant wsdl2java 명령어로부터 생성된 Holder 클래스들은 사용자가 작성한 것과는 다르다는 것을 유념한다.

import com.test.calc.*; // generated by ant process-wsdl 
import com.test.calc.holders.*; // generated by ant process-wsdl 

public class CalcClient {
    public static void main(String[] args) {
        CalcClient calc = new CalcClient();

        if (args.length != 3) {
            System.out.println("usage: java CalcClient num1 op num2");
            System.out.println(" where op is one of " + "'plus', 'minus', 
                  'mult', 'div'");
            System.exit(1);
        }

        try {
            calc.run(args);
        } catch (Exception e) {
            System.err.println(e.toString());
            e.printStackTrace();
        }
    }

    public void run(String[] args) throws Exception {
        CalculatorIF port = new Calculator3Service_Impl().getCalculatorIFPort();
        CalcData data = new CalcData();
        CalcDataHolder dataHolder = new CalcDataHolder(data);

        data.setNum1((new Double(args[0])).doubleValue());
        data.setNum2((new Double(args[2])).doubleValue());
        data.setOp(args[1]);
        
        port.calc(dataHolder);
        System.out.println(dataHolder.value.getResult());
    }
}

JEUS 웹 서비스에서 java.rmi.RemoteException 또는 java.lang.Exception 클래스를 상속한 클래스를 SOAP Fault로 사용할 수 있다. SOAP Fault는 SOAP 메시지를 통해 에러나 상태 정보를 보내기 위해 사용된다. 보다 자세한 내용은 SOAP 1.1 스펙의 "4.4 SOAP Fault"을 참고한다.

Exception 상태를 웹 서비스 애플리케이션의 클라이언트로 전송하려면 웹 서비스에서 java.rmi.RemoteException이나 사용자 정의 Exception을 발생하도록 지정한다. 발생한 Exception은 자동으로 SOAP Fault로 감싸져서 SOAP 메시지의 body에 포함되어 웹 서비스 클라이언트로 전송된다.

CalcService 예제에서 에러 상황이 발생할 경우 –9999.0 값을 반환값으로 사용하였던 소스 코드는 다음과 같이 변경할 수 있다.


calc() 메소드는 알려지지 않은 연산자가 지정되거나 나누기 값이 0일 때 java.rmi.RemoteException을 발생하도록 만들었다. 웹 서비스 클라이언트 프로그램에서 java.rmi.RemoteException 또는 java.lang.Exception을 잡을 때 사용할 수 있다.

웹 서비스 클라이언트는 Attachment를 SOAP 메시지에 첨부하여 전송할 수 있다. JAX-RPC 스펙은 Attachment의 MIME 타입에 상응하는 Java 타입 매핑을 정의하고 있지만, 경우에 따라 웹 서비스 클라이언트가 MIME 타입에 관계없이 항상 javax.activation.DataHandler 타입으로 매핑하여 사용할 수도 있다.

본 절에서는 WSDL-to-Java 매핑 툴을 사용하여 MIME 타입을 항상 DataHandler 타입으로 매핑하는 방법을 설명한다.

다음과 같이 MIME part를 가진 웹 서비스의 WSDL이 있다.

<message name="submission">
    <part name="title" type="xsd:string" />
    <part name="price" type="xsd:float" />
    <part name="attachment" type="xsd:hexBinary" />
</message>
. . .
<operation name="submit">
    . . .
    <input>
        . . .
        <mime:part>
            <mime:content part="attachment" type="application/xml" />
        </mime:part>
        . . .
    </input>
    . . .
</operation>

기본적으로 이러한 WSDL을 가지고 wsdl2java 툴을 사용하여 SEI를 생성하면 다음과 같이 MIME 타입 "application/xml"은 javax.xml.transform.Source 타입으로 매핑된다.

public interface SubmitBook extends java.rmi.Remote {
  public String submit(String title, float price,
                       javax.xml.transform.Source attachment)
                       throws java.rmi.RemoteException;
}

Ant Task, wsdl2java에서 attribute, dataHandlerOnly="true"로 설정하거나 Command Line 툴에서 –datahandleronly 옵션을 사용하면, 다음과 같이 MIME 타입에 상관없이 part는 항상 javax.activation.DataHandler 타입으로 매핑된다.

public interface SubmitBook extends java.rmi.Remote {
  public String submit(String title, float price,
                       javax.activation.DataHandler attachment)
                       throws java.rmi.RemoteException;
}

따라서 웹 서비스 클라이언트 개발자는 DataHandler 타입으로 Attachment를 송부해야 한다.

다음은 DataHandler 타입을 사용한 웹 서비스 클라이언트 예제이다.

// Creates a FileInputStream from the specified path name
FileInputStream inputStream =
    new FileInputStream(new File("attachment/book.xml"));
DataHandler dataHandler = new DataHandler(inputStream, "application/xml");

// Get a Service port
SubmitBook port = new SubmitBookService_Impl().getSubmitBookPort();
String result = port.submit("Sample for a option: datahandleronly", 
                12.34f, dataHandler);
System.out.println("response = " + result);

JAX-RPC 스펙에는 XML 타입에 대한 Java 타입 매핑을 정의하고 있다. 그러나 WSDL의 타입에 의해 매핑된 Java 타입을 사용하기 보다는 SOAPElement를 직접 구성하여 메시지를 전송하는 것이 더욱 편리할 수도 있다.

본 절에서는 WSDL-to-Java 매핑 툴을 사용하여 XML 타입에 상관없이 javax.xml.soap.SOAPElement를 사용하는 방법을 설명한다.

다음과 같은 Document/Literal로 기술된 WSDL이 있다.

<definitions name="BookQuoteService" ... >
    <types>
        <xsd:schema targetNamespace="...">
            <xsd:complexType name="Book">
                <xsd:sequence>
                    <xsd:element name="title" type="xsd:string" />
                    <xsd:element name="isbn" type="xsd:string" />
                    <xsd:element name="authors" type="xsd:string" />
                </xsd:sequence>
            </xsd:complexType>
            <xsd:element name="Book" type="mh:Book" />
            <xsd:element name="Result" type="xsd:float" />
        </xsd:schema>
    </types>

    <message name="getBookPriceRequest">
        <part name="book" element="mh:Book" />
    </message>
    <message name="getBookPriceResponse">
        <part name="result" element="mh:Result" />
    </message>
    . . .
  
    <binding name="BookServiceSoapBinding" type="mh:BookQuote">
        <soap:binding style="document" ... />
        <operation name="getBookPrice">
            <input>
                <soap:body use="literal" ... />
            </input>
            <output>
                <soap:body use="literal" ... />
            </output>
        </operation>
    </binding>
    . . .
</definitions>

이 WSDL로부터 wsdl2java로 생성한 SEI는 다음과 같이 Object 타입의 Input 파라미터를 갖는다.

public interface BookQuote extends java.rmi.Remote {
    public float getBookPrice(sample.nodatabinding.stub.Book book)
    throws java.rmi.RemoteException;
}

만약 wsdl2java로 생성한 SEI를 생성할 때 Ant Task, wsdl2java에서 attribute, noDataBinding="true"로 설정하거나, Command Line 툴에서 –nodatabinding 옵션을 사용하면 다음과 같이 XML 타입에 상관없이 Input 파라미터 및 Return value 타입은 javax.xml.soap.SOAPElement가 된다.

public interface BookQuote extends java.rmi.Remote {
public javax.xml.soap.SOAPElement
    getBookPrice(javax.xml.soap.SOAPElement book)
    throws java.rmi.RemoteException;
}

wsdl2java의 nodatabinding 옵셥은 Document/Literal의 WSDL에서만 유효하다. 이 경우 웹 서비스 클라이언트 개발자는 SOAPElement 타입으로 메시지를 직접 구성해야 한다.

다음은 SOAPElement 타입을 사용한 웹 서비스 클라이언트 예제이다.

// Creates a FileDataSource from the specified path name
SOAPFactory factroy = SOAPFactory.newInstance();

// Create a SOAPElement object
SOAPElement book = factroy.createElement( "Book", "mh", 
                   "http://www.tmaxsoft.com/j2eews/BookQuote");

SOAPElement title = factroy.createElement("title");
title.addTextNode("Sample for a option: nodatabinding");
book.addChildElement(title);

SOAPElement isbn = factroy.createElement("isbn");
isbn.addTextNode("123-456-789");
book.addChildElement(isbn);

SOAPElement authors = factroy.createElement("authors");
authors.addTextNode("TmaxSoft Co., Ltd.");
book.addChildElement(authors);

// Get a Service port
BookQuote port = new BookQuoteService_Impl().getBookQuotePort();
SOAPElement price = port.getBookPrice(book);
System.out.println("price = " + price.getValue());