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

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

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

1. 개요

다음은 웹 서비스와 웹 서비스 클라이언트에서 사용하는 데이터 타입의 특징이다.

  • Java와 XML 타입 매핑

    XML 인스턴스를 Java 객체로 직렬화 또는 역직렬화((de)serialize)하기 위해서는 XML의 타입과 Java 클래스들의 타입들과의 타입 매핑이 필요하다. JEUS 웹 서비스는 JAX-RPC 스펙에 설명된 Java와 표준 XML 타입 매핑을 따르고 있다.

  • JAX-RPC Value 타입 사용

    JAX-RPC Value 타입은 웹 서비스의 요청이나 응답에서 값으로써 전달될 수 있는 타입을 말한다. JAX-RPC Value 타입은 일반적으로 사용자 정의 JavaBeans 컴포넌트로 표현된다. JEUS 웹 서비스는 사용자가 작성한 JavaBeans 타입을 웹 서비스의 파라미터와 반환 값으로서 사용할 수 있도록 하고 있다.

  • 입출력(In/Out) 파라미터로서 Holder의 사용

    입출력(In/Out) 파라미터는 웹 서비스를 실행할 때 입력은 물론 출력에서 사용되는 파라미터이다. 웹 서비스가 여러 출력값들을 반환해야 할 경우 입/출력 파라미터를 사용할 수 있다. JEUS 웹 서비스는 입/출력 파라미터를 표준 JAX-RPC Holder 클래스를 사용하여 지원하고 있다.

  • SOAP Fault로서의 Exception 사용

    웹 서비스 실행 중 어떤 에러가 발생하게 되면 웹 서비스 클라이언트에게 그 내용이 전달되어야 한다. SOAP 표준에서는 이런 목적을 위한 SOAP Fault를 정의하고 있다. JEUS 웹 서비스는 표준 SOAP Fault를 지원한다.

2. Java와 XML 타입 매핑

지금까지 하나의 타입만(String만)을 파라미터와 반환값으로 사용하는 웹 서비스 예제를 보았다.

JEUS 웹 서비스는 Java 타입을 XML 또는 WSDL 정의로 매핑한다. 예를 들어 JEUS 웹 서비스는 java.lang.String 클래스를 XML xsd:string 데이터 타입으로 매핑한다.

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

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

2.1. 내장 타입 매핑

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

XML 데이터 타입 Java 데이터 타입

xsd:string

java.lang.String

xsd:boolean

boolean, java.lang.Boolean *

xsd:double

double, java.lang.Double *

xsd:float

float, java.lang.Float *

xsd:int

int, java.lang.Integer

xsd:integer

java.math.BigInteger

xsd:long

long, java.lang.Long *

xsd:short

short, java.lang.Short *

xsd:byte

byte, java.lang.Byte

xsd:Decimal

java.math.BigDecimal

xsd:base64Binary

byte[ ]

xsd:hexBinary

byte[ ]

xsd:QName

javax.xml.rpc.namespace.QName

xsd:dateTime

java.util.Calendar

xsd:gYearMonth

java.util.Calendar

xsd:gYear

java.util.Calendar

xsd:gMonthDay

java.util.Calendar

xsd:anyURI

java.net.URI (JDK 1.4 or over) / java.lang.String (JDK 1.4)

xsd:duration

java.lang.String

xsd:name

java.lang.String

xsd:NCName

java.lang.String

xsd:NMTOKEN

java.lang.String

xsd:nomalizedString

java.lang.String

xsd:time

java.util.Calendar

xsd:token

java.lang.String

xsd:unsignedByte

short

xsd:unsignedLong

java.math.BigInteger

xsd:unsignedInt

long

xsd:unsignedShort

int

SOAP-ENC:base64

byte[ ]

SOAP-ENC:string

java.lang.String

SOAP-ENC:boolean

boolean, java.lang.Boolean *

SOAP-ENC:double

double, java.lang.Double *

SOAP-ENC:float

float, java.lang.Float *

SOAP-ENC:int

int, java.lang.Integer *

SOAP-ENC:long

long, java.lang.Long *

SOAP-ENC:short

short, java.lang.Short *

SOAP-ENC:byte

byte, java.lang.Byte *

SOAP-ENC:interger

java.math.BigInteger

SOAP-ENC:decimal

java.math.BigDecimal

SOAP-ENC:Array

java.math.BigDecimal

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

만약 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);

2.2. 배열

JEUS 웹 서비스는 JAX-RPC 타입에서 정의한 배열들을 지원한다. 예를 들어 int[ ]와 String[ ]이나 다차원 배열인 java.math.BigDecimal[ ][ ]도 지원한다.

2.3. 사용자 정의 타입 : JAX-RPC Value Type

JEUS 웹 서비스는 애플리케이션을 위해 작성한 모든 사용자 정의 타입을 지원한다. JAX-RPC 스펙에는 이러한 클래스들을 Value Type이라고 한다.

JEUS 웹 서비스에서 이것을 지원하기 위해서는 사용자 정의 클래스들은 다음 규칙을 따라야 한다.

  • 파라미터 없는 public default 생성자를 가져야 한다.

  • 직접 또는 간접적으로 java.rmi.Remote를 구현해서는 안 된다.

  • 멤버 필드들의 타입은 JEUS 웹 서비스가 지원하는 타입이어야 한다.

클래스는 public, private 또는 protected 필드들을 포함할 수 있다. 웹 서비스 호출 중 전달되는 값을 위해서 필드는 다음 조건을 충족해야 한다.

  • public 필드는 final이나 transient가 될 수 없다.

  • public 필드가 아닌 것은 관련된 getter와 setter 메소드를 가져야 한다.

위의 규칙을 따르는 JavaBeans 컴포넌트 또한 지원된다.

3. JAX-RPC Value 타입의 사용

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

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

3.1. JAX-RPC Value 타입을 사용하는 웹 서비스 생성

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

JAX-RPC Value 타입을 사용하는 웹 서비스 : <Calculator.java>
package calc;

public class Calculator implements CalculatorIF {
    public Calculator() { }
    public double calc(CalcData data) {
        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;
        }
        return ret;
    }
}

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

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

JAX-RPC Value 타입을 사용하는 웹 서비스 : <CalcData.java>
package calc;

public class CalcData {
    private double num1;
    private double num2;
    private String op;

    public CalcData() { }
    public double getNum1() { return num1; }
    public double getNum2() { return num2; }
    public String getOp() { return op; }

    public void setNum1(double n) { num1 = n; }
    public void setNum2(double n) { num2 = n; }
    public void setOp(String s) { op = s; }
}

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

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

JAX-RPC Value 타입을 사용하는 웹 서비스 : <CalculatorIF.java>
package calc;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface CalculatorIF extends Remote {
    public double calc(CalcData data) throws RemoteException;
}

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

$ ant compile

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

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

ant wsear

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

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

3.2. JAX-RPC Value 타입을 사용하는 웹 서비스 클라이언트 생성

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

ant wsdl2java

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

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

JAX-RPC Value 타입을 사용하는 웹 서비스 클라이언트 : <CalcClient.java>
import com.test.calc.*;
import javax.xml.rpc.soap.SOAPFaultException;

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 (SOAPFaultException e) {
            System.err.println("faultcode = " + e.getFaultCode());
            System.err.println("faultString = " + e.getFaultString());
        } catch (Exception e) {
            System.err.println(e.toString());
            e.printStackTrace();
        }
    }
    public void run(String[] args) throws Exception {
        CalculatorIF port = new
        Calculator1Service_Impl().getCalculatorIFPort();
        CalcData data = new CalcData();
        data.setNum1((new Double(args[0])).doubleValue());
        data.setNum2((new Double(args[2])).doubleValue());
        data.setOp(args[1]);

        double ret = port.calc(data);
        System.out.println(ret);
    }
}

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

$ ant build

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

$ ant runclient

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

2.0

4. Holder 클래스

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

4.1. 내장 Holder 클래스

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

Holder Class Java Data Type

javax.xml.rpc.holders.BooleanHolder

boolean

javax.xml.rpc.holders.ByteHolder

byte

javax.xml.rpc.holders.ShortHolder

short

javax.xml.rpc.holders.IntHolder

int

javax.xml.rpc.holders.LongHolder

long

javax.xml.rpc.holders.FloatHolder

float

javax.xml.rpc.holders.DoubleHolder

double

javax.xml.rpc.holders.BigDecimalHolder

java.math.BigDecimal

javax.xml.rpc.holders.BigIntegerHolder

java.math.BigInteger

javax.xml.rpc.holders.ByteArrayHolder

byte[ ]

javax.xml.rpc.holders.CalendarHolder

java.util.Calendar

javax.xml.rpc.holders.QNameHolder

javax.xml.namespace.QName

javax.xml.rpc.holders.StringHolder

java.lang.String

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

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

내장 Holder 클래스 사용 예 : <Calculator.java>
package calc;

import javax.xml.rpc.holders.DoubleHolder;

public class Calculator implements CalculatorIF {
    public Calculator() { }

    public void calc(CalcData data, DoubleHolder result){
        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;
        }

        result.value = ret;
    }
}

소스 코드는 내장 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의 run() 메소드
    public void run(String[] args) throws Exception {
        CalculatorIF port = new CalcService_Impl().getCalculatorIFPort();
        CalcData data = new CalcData();
        data.setNum1((new Double(args[0])).doubleValue());
        data.setNum2((new Double(args[2])).doubleValue());
        data.setOp(args[1]);

        DoubleHolder ret = new DoubleHolder();
        port.calc(data, ret);
        System.out.println(ret.value);
    }

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

4.2. 사용자 정의 타입을 위한 Holder 클래스 작성

다음은 사용자가 작성한 클래스의 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 메소드를 멤버로 추가하였다.

사용자 정의 타입 Holder 클래스 작성 예제 : <CalcData.java>
package calc;

public class CalcData {
    private double num1;
    private double num2;
    private String op;
    private double result;

    public CalcData() { }
    public double getNum1() { return num1; }
    public double getNum2() { return num2; }
    public String getOp() { return op; }
    public double getResult() { return result; }

    public void setNum1(double n) { num1 = n; }
    public void setNum2(double n) { num2 = n; }
    public void setOp(String s) { op = s; }
    public void setResult(double n) { result = n; }
}

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

사용자 정의 타입 Holder 클래스 작성 예제 : <CalcDataHolder.java>
package calc;
import javax.xml.rpc.holders.Holder;

public class CalcDataHolder implements Holder {
    public CalcData value;

    public CalcDataHolder() {
    }

    public CalcDataHolder(CalcData value) {
        this.value = value;
    }
}

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());
    }
}

5. Exception과 SOAP Fault

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 값을 반환값으로 사용하였던 소스 코드는 다음과 같이 변경할 수 있다.

Exception 생성 예제 : <Calculator.java>
package calc;

import java.rmi.RemoteException;

public class Calculator {
    public Calculator() { }
    public double calc(CalcData data)
        throws RemoteException, DevideByZeroException {
        String op = data.getOp();
        double num1 = data.getNum1();
        double num2 = data.getNum2();
        double ret = 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;
            else
                throw new DevideByZeroException("divide by zero");
        } else {
            throw new RemoteException("invalid opertion : " + op);
        }
        return ret;
    }
}

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

6. MIME 타입을 DataHandler 타입으로 매핑

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

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

6.1. Wsdl2java에서 dataHandlerOnly 옵션 사용

다음과 같이 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);

7. Doc/Literal에서 데이터 바인딩을 사용하지 않기

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

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

7.1. Wsdl2java에서 noDataBinding 옵션 사용

다음과 같은 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());