 |
|
실제 스키마 예제
독립형 스키마 정의는 간단한 데모를 보여 주는 데는 적합하지만 엔터프라이즈 애플리케이션에서
널리 사용되는 복잡한 스키마 정의에 적용된 도구가 어떻게 작동하는지를 보여 주기에는 많은 한계를
가지고 있다. 따라서 이제 산업 표준 HR-XML 스키마 정의 중 하나의 형태로 된 좀 더 현실적인 예제를
살펴볼 것이다.
HR-XML TimeCard 스키마
HR-XML Consortium은 인적 자원에 대한 XML 표현을 위한 오픈 표준을 개발하기 위해 설립된
조직이다. 이 조직은 110개 이상의 회원사를 대표하며 거의 50개에 달하는 기술 회사에서 이
표준에 대한 인증을 받았다.
이 튜토리얼에서 사용하는 HR-XML 스키마는 157개의 스키마로 구성되어 있으며, 여기에는
최상위 레벨 문서 정의와 공통 구성 요소가 혼합되어 있다. CodeGen에서는 이 정도 개수의
스키마를 쉽게 처리할 수 있지만 생성된 클래스의 수와 상호 관계의 복잡도로 인해 스키마
처리의 주요 측면이 명확하게 전달되지 않을 수 있다. 이러한 세부 사항에 집중하기 위해
이 튜토리얼에서는 총 7개의 스키마 정의로 구성된 HR-XML의 서브셋인 TimeCard
요소를 사용하며, 이 요소는 하나의 최상위 레벨 문서 정의와 TimeCard
정의의 일부로 참조되는 공통 구성 요소로 구성되어 있다.
이 튜토리얼에서 사용하는 HR-XML 스키마 정의의 서브셋은 hrxml/schemas 디렉토리에 있다. Listing 7은
TimeCard 요소 정의의 기본 스키마를 편집한 버전이다. HR-XML 스키마 양식의
샘플인 이 예제에서는 중첩 및 전역 유형 정의를 혼합해서 사용하고 있고 첫 번째 예제보다 더 넓은 범위의
스키마 구조가 포함되어 있다. 추가된 구조는 다음과 같다.
<xs:choice> 컴포지터(TimeCardType 정의 내에 포함된 complexType 중 일부 참조)
<xs:any> 파티클(listing 앞쪽의 AdditionalDataType 정의 참조)
<xs:simpleType> <union>(listing 끝쪽의 TimeCardDuration 정의 참조)
- 비열거형
<xs:simpleType> 제한 사항
Listing 7. HR-XML TimeCard 스키마
<xs:schema targetNamespace="http://ns.hr-xml.org/2007-04-15" ...
elementFormDefault="qualified" version="2007-04-15">
<xs:import namespace="http://www.w3.org/XML/1998/namespace" ...>
<xs:include schemaLocation="../CPO/EntityIdType.xsd"/>
...
<xs:complexType name="AdditionalDataType" mixed="true">
...
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:any namespace="##any" processContents="strict" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
...
<xs:element name="TimeCard" type="TimeCardType"/>
<xs:complexType name="TimeCardType">
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
<xs:element name="ReportedResource">
<xs:complexType>
<xs:choice>
<xs:element name="Person" type="TimeCardPersonType"/>
<xs:element name="Resource">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType"
minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ResourceName" type="xs:string" minOccurs="0"/>
<xs:element name="AdditionalData" type="AdditionalDataType" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="type" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="ReportedTime" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="PeriodStartDate" type="AnyDateTimeType"/>
<xs:element name="PeriodEndDate" type="AnyDateTimeType"/>
<xs:element name="ReportedPersonAssignment" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:choice maxOccurs="unbounded">
<xs:element name="TimeInterval">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="EntityIdType" minOccurs="0"/>
<xs:element name="StartDateTime" type="AnyDateTimeType"/>
<xs:choice>
<xs:sequence>
<xs:element name="EndDateTime" type="AnyDateTimeType"/>
<xs:element name="Duration" type="TimeCardDuration" minOccurs="0"/>
</xs:sequence>
<xs:element name="Duration" type="TimeCardDuration"/>
</xs:choice>
<xs:element name="PieceWork" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
<xs:element name="RateOrAmount" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
<xs:element name="Allowance" minOccurs="0" maxOccurs="unbounded">
...
</xs:element>
...
</xs:sequence>
<xs:attribute name="type" type="xs:string" use="required"/>
...
</xs:complexType>
</xs:element>
<xs:element name="TimeEvent">
...
</xs:element>
<xs:element name="Expense">
...
</xs:element>
<xs:element name="Allowance">
...
</xs:element>
</xs:choice>
...
</xs:sequence>
...
</xs:complexType>
</xs:element>
...
</xs:sequence>
<xs:attribute ref="xml:lang"/>
</xs:complexType>
...
<xs:simpleType name="TimeCardDuration">
<xs:union memberTypes="xs:duration xs:decimal"/>
</xs:simpleType>
</xs:schema> |
 |

|
생성된 TimeCard 코드
hrxml 디렉토리에 있는 Ant build.xml 파일은 TimeCard 스키마의 기본
코드 생성(나중에 설명할 기본 생성 및 사용자 정의 예제 포함)을 위한 Ant 대상을 정의한다. samples
디렉토리에는 테스트 프로그램인 org.jibx.hrxml.Test도 있다. 이 프로그램은
생성된 데이터 모델 클래스를 사용하여 샘플 문서를 언마샬링했다가 다시 마샬링한 후 원래 문서와 결과를
비교한다. 그리고 samples 디렉토리에는 HR-XML 배포판의 테스트 문서 세트가 있다. codegen
대상은 기본값을 사용하여 CodeGen을 실행하고, compile은 생성된 코드와 테스트
코드를 컴파일하며, bind는 JiBX 바인딩을 컴파일하고, roundtrip은
샘플 문서에 대해 테스트 프로그램을 실행한다. full 태스크를 사용하여 전체
단계를 차례대로 실행할 수도 있다.
스키마에서 코드를 생성하는 대부분의 경우에는 각 complexType 정의와
열거형 simpleType에 대해 별도의 클래스가 생성된다. 그러나 CodeGen에서는
참조를 조사하여 정의를 인라인 처리하고(가능한 경우) 포함되거나 가져온 스키마 정의에서 사용되지 않는
정의를 무시하기 때문에 생성된 클래스의 수를 줄일 수 있다. TimeCard 스키마의
경우 총 10개의 전역(이름지정됨) complexType, 23개의 추가 로컬(익명) complexType 및
8개의 열거형 simpleType이 있다. 생성된 기본 데이터 모델은 스키마 구성
요소의 수를 기반으로 예상한 개수보다 약간 적은 15개의 최상위 레벨 클래스와 23개의 내부 클래스로
구성되어 있다. 나중에 스키마 구성 요소 중 일부가 필요하지 않은 경우 사용자 정의를 통해 데이터 모델을
단순화하는 몇 가지 방법을 살펴볼 것이다.
<xs:choice> 처리
Listing 8에서는 CodeGen이 TimeCardType complexType 정의에 있는 두
요소에 대한 선택을 처리하는 방법을 보여 준다. 기본적으로 CodeGen은 선택 변수를 사용하여 현재
활성 상태에 있는 선택 항목을 추적한다. 선택 항목에 포함된 값에 대한 set 메소드를 통해 현재 선택
항목에 대한 새 값을 작성할 수 있다. 그러나 선택 항목을 직접 변경할 수는 없다(시도할 경우
IllegalStateException 발생). 이미 설정되어 있는 현재 선택 항목을
변경하려면 먼저 선택 상태를 재설정하는 삭제 메소드(여기에서는 clearReportedResourceSelect())를
호출해야 한다.
Listing 8. HR-XML TimeCard 생성 코드 샘플
/**
* Schema fragment(s) for this class:
* <pre>
* <xs:complexType xmlns:ns="http://ns.hr-xml.org/2007-04-15"
* xmlns:ns1="http://www.w3.org/XML/1998/namespace"
* xmlns:xs="http://www.w3.org/2001/XMLSchema" name="TimeCardType">
* <xs:sequence>
* <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/>
* <xs:element name="ReportedResource">
* <xs:complexType>
* <xs:choice>
* <xs:element type="ns:TimeCardPersonType" name="Person"/>
* <xs:element name="Resource">
* <!-- Reference to inner class Resource -->
* </xs:element>
* </xs:choice>
* </xs:complexType>
* </xs:element>
* ...
*/
public class TimeCardType
{
private EntityIdType id;
private int reportedResourceSelect = -1;
private final int REPORTED_RESOURCE_PERSON_CHOICE = 0;
private final int RESOURCE_CHOICE = 1;
private TimeCardPersonType reportedResourcePerson;
private Resource resource;
...
private void setReportedResourceSelect(int choice) {
if (reportedResourceSelect == -1) {
reportedResourceSelect = choice;
} else if (reportedResourceSelect != choice) {
throw new IllegalStateException(
"Need to call clearReportedResourceSelect() before changing existing choice");
}
}
/**
* Clear the choice selection.
*/
public void clearReportedResourceSelect() {
reportedResourceSelect = -1;
}
/**
* Check if ReportedResourcePerson is current selection for choice.
*
* @return <code>true</code> if selection, <code>false</code> if not
*/
public boolean ifReportedResourcePerson() {
return reportedResourceSelect == REPORTED_RESOURCE_PERSON_CHOICE;
}
/**
* Get the 'Person' element value.
*
* @return value
*/
public TimeCardPersonType getReportedResourcePerson() {
return reportedResourcePerson;
}
/**
* Set the 'Person' element value.
*
* @param reportedResourcePerson
*/
public void setReportedResourcePerson(
TimeCardPersonType reportedResourcePerson) {
setReportedResourceSelect(REPORTED_RESOURCE_PERSON_CHOICE);
this.reportedResourcePerson = reportedResourcePerson;
}
/**
* Check if Resource is current selection for choice.
*
* @return <code>true</code> if selection, <code>false</code> if not
*/
public boolean ifResource() {
return reportedResourceSelect == RESOURCE_CHOICE;
}
/**
* Get the 'Resource' element value.
*
* @return value
*/
public Resource getResource() {
return resource;
}
/**
* Set the 'Resource' element value.
*
* @param resource
*/
public void setResource(Resource resource) {
setReportedResourceSelect(RESOURCE_CHOICE);
this.resource = resource;
} |
대부분의 애플리케이션에서 이 선택 처리 방식은 올바르게 수행된다. 즉, 사용자가 하나의
선택에서 둘 이상의 대체 항목을 설정할 수 없게 된다. 그러나 사용자 정의를 사용하여 기본
선택 처리 방식을 수정할 수 있으므로 이 선택 처리 방식을 원하지 않는 경우 쉽게 변경할
수 있다. choice-check 속성은 생성된 코드에서 <xsd:choice>의
선택 상태를 선택하는 방법을 제어한다. choice-check="disable"
값은 모든 선택 항목을 비활성화하고 선택 상태를 추적하지 않으며, 사용자가 각 선택 항목에
대해 하나의 값만을 설정할 수 있도록 한다. choice-check="checkset"는
Listing 8의 기본 처리와 일치한다. 이 경우에는 set 메소드가 현재
설정을 검사하고 예외를 발생시킨다. 또한 choice-check="checkboth"는
get 메소드가 호출될 때 선택 상태를 검사하여 get 메소드가 현재 선택 상태와 일치하지 않는
경우 예외를 발생시킨다. 마지막으로, choice-check="override"는
다른 상태가 이미 설정된 경우 예외를 발생시키는 대신 선택 항목의 값이 설정되어 있으면
현재 상태를 항상 변경하도록 기본 처리를 변경한다.
choice-exposed 사용자 정의 속성은 현재 선택 상태를 추적하는
choice-check 설정과 함께 작동한다. choice-exposed="false"
값은 Listing 8의 기본 코드 생성과 동일하게 선택 상태 상수, 상태
변수 값 및 상태 변경 메소드를 모두 private으로 유지한다. choice-exposed="true"는
이러한 항목을 모두 public으로 설정하고 상태 변수에 대한 get 메소드를 추가한다. 이렇게
하면 여러 개의 if 명령문을 사용하는 대신 Java switch
명령문을 사용하여 현재 상태에 따라 다른 코드를 쉽게 실행할 수 있다.
이러한 속성은 사용자 정의의 모든 레벨에서 사용할 수 있으며 경우에 따라 조금씩 다른
작업을 수행하도록 유지하면서 가장 바깥쪽 사용자 정의의 모든 생성된 코드에 대한 동작을
쉽게 설정할 수 있다.
<xs:any> 및 mixed="true" 처리
여러 엔터프라이즈 스키마와 마찬가지로 HR-XML 스키마도 <xs:any>
스키마 구성 요소를 사용하여 원래 스키마와는 별개로 사용자가 정의할 수 있는 데이터의 확장 지점을
만든다. 기본적으로 CodeGen은 org.w3c.dom.Element 오브젝트(또는 <xs:any>의
maxOccurs 값이 1보다 큰 경우 Element 목록)를
사용하여 <xs:any> 스키마 구성 요소를 처리한다. Element
오브젝트는 임의의 XML 요소(모든 속성, 네임스페이스 선언 및 컨텐츠 포함)를 표현하는 데 사용할 수
있으므로 스키마 정의와 일치하는 문서 작업을 수행하는 데 필요한 모든 유연성을 제공한다.
Listing 9에서는 Listing 7 스키마 샘플의 <xs:any>
구성 요소와 일치하는 생성된 코드를 보여 준다. <xs:any>가 maxOccurs="unbounded"를
사용하므로 생성된 코드는 Element 목록을 사용한다.
Listing 9. <xs:any> 생성 코드 샘플
/**
* ...
* Schema fragment(s) for this class:
* <pre>
* <xs:complexType xmlns:xs="http://www.w3.org/2001/XMLSchema" mixed="true"
* name="AdditionalDataType">
* <xs:sequence>
* <xs:any minOccurs="0" maxOccurs="unbounded" processContents="strict"
* namespace="##any"/>
* </xs:sequence>
* <xs:attribute type="xs:string" name="type"/>
* </xs:complexType>
* </pre>
*/
public class AdditionalDataType
{
private List<Element> anyList = new ArrayList<Element>();
private String type;
/**
* Get the list of sequence items.
*
* @return list
*/
public List<Element> getAny() {
return anyList;
}
/**
* Set the list of sequence items.
*
* @param list
*/
public void setAny(List<Element> list) {
anyList = list;
}
...
} |
CodeGen은 Listing 9의 스키마 정의 중 일부 특성을 무시하거나 부분적으로만 처리한다. 먼저 바깥쪽
<xs:complexType> 정의는 mixed="true"를
지정한다. 이는 문자 데이터를 <xs:any> 파티클로 표현되는 요소와
혼합할 수 있음을 뜻한다. CodeGen에서 생성된 데이터 모델에는 그러한 문자 데이터 컨텐츠를 저장할 수
없으므로 이러한 컨텐츠는 문서가 언마샬링될 때 버려진다. 두 번째로 <xs:any>는
processContents="strict"를 사용한다. 이는 인스턴스 문서에 존재하는 모든
요소에 고유한 스키마 정의가 있어야 함을 뜻한다. CodeGen은 다른 방식의 <xs:any>
처리(아래 참조)를 사용하여 비슷한 효과를 얻을 수도 있지만 이 속성을 무시한다. 또한 CodeGen은 <xs:any>
네임스페이스 제한 사항도 무시한다. Listing 9에서는 namespace="##any"를
사용한다. 이는 곧 <xs:any>와 일치하는 요소가 네임스페이스 제한을 받지
않는다는 것을 뜻한다. 그러나 값이 namespace="##other"인 경우에도 같은 결과가 발생한다.
사용자 정의의 모든 레벨에서 any-handling 사용자 정의 속성을 사용하여
다른 방식의 <xs:any> 처리를 선택할 수 있다. any-handling="discard"
값은 생성된 데이터 모델에서 <xs:any>를 무시하고 언마샬링이 발생할
때 <xs:any>에 해당하는 모든 요소를 버린다. any-handling="dom"은
<xs:any>와 일치하는 요소를 나타내는 org.w3c.dom.Element를
사용하여 기본 처리와 일치한다. 마지막으로, any-handling="mapped"는 <xs:any>와
일치하는 각 요소에 대해 전역 스키마 정의가 필요한 코드를 생성한다(대략적으로 processContents="strict"
스키마 조건에 해당). 이 마지막 경우에는 데이터 모델이 java.lang.Object와
전역 스키마 정의와 일치하는 오브젝트의 실제 런타임 유형을 사용하여 요소를 나타낸다.
<xs:simpleType> 처리
스키마에서 코드를 생성하는 대부분의 경우와 마찬가지로 CodeGen은 <xs:simpleType>
정의의 많은 부분을 무시하거나 부분적으로만 처리한다. <xs:simpleType> 제한 사항은
이 제한된 지원의 한 예이다. 스키마에 정의된 simpleType 제한 사항(길이 제한, 값 범위 및
정규식 패턴 등)의 많은 변형 중 <xs:enumeration> 제한 사항만 현재 생성된 데이터 모델에
적용된다.
<xs:simpleType> <union>도 현재 CodeGen에서 무시된다. Listing 10에서는
<xs:union> 참조와 일치하는 생성된 코드를 보여 주며 listing의 맨 아래에는 코드와
일치하는 원래 스키마를 보여 준다. Listing 10을 보면 union 유형(listing에 표시된 TimeCardDuration
유형 및 AnyDateTimeType 포함)에 대한 각 참조가 생성된 코드에서 단순 String
값으로 표현된다는 것을 알 수 있다.
Listing 10. <xs:union> 생성 코드 샘플 및 원래 스키마
/**
* Schema fragment(s) for this class:
* <pre>
* <xs:element xmlns:ns="http://ns.hr-xml.org/2007-04-15"
* xmlns:xs="http://www.w3.org/2001/XMLSchema" name="TimeInterval">
* <xs:complexType>
* <xs:sequence>
* <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/>
* <xs:element type="xs:string" name="StartDateTime"/>
* <xs:choice>
* <xs:sequence>
* <xs:element type="xs:string" name="EndDateTime"/>
* <xs:element type="xs:string" name="Duration" minOccurs="0"/>
* </xs:sequence>
* <xs:element type="xs:string" name="Duration"/>
* </xs:choice>
* ...
* </pre>
*/
public static class TimeInterval
{
private EntityIdType id;
private String startDateTime;
private int choiceSelect = -1;
private final int END_DATE_TIME_CHOICE = 0;
private final int DURATION_CHOICE = 1;
private String endDateTime;
private String duration;
private String duration1;
...
...
<xsd:element name="TimeInterval">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Id" type="EntityIdType" minOccurs="0"/>
<xsd:element name="StartDateTime" type="AnyDateTimeType"/>
<xsd:choice>
<xsd:sequence>
<xsd:element name="EndDateTime" type="AnyDateTimeType"/>
<xsd:element name="Duration" type="TimeCardDuration" minOccurs="0"/>
</xsd:sequence>
<xsd:element name="Duration" type="TimeCardDuration"/>
</xsd:choice>
...
<xsd:simpleType name="TimeCardDuration">
<xsd:union memberTypes="xsd:duration xsd:decimal"/>
</xsd:simpleType>
|
 |

|
스키마 수정
Listing 10의 상단에 있는 Javadoc에 포함된 스키마와 listing의 하단에
있는 실제 스키마를 비교해 보면 원래 스키마의 union simpleType 참조가
Javadoc 버전에서 xs:string 참조로 바뀌었다는 것을 알 수 있다. 이는
CodeGen에 의해 일부 유형이 스키마 구조로 변환되었음을 나타낸다. <xs:enumeration>을
제외한 <union> simpleType 및 simpleType
제한 사항은 CodeGen 작업에 하드 코딩된다. 다른 변환은 사용자 정의에 따라 제어된다. 두 경우 모두
Javadoc에 포함된 스키마에 변환된 스키마가 항상 표시된다. 이는 변환된 스키마가 코드를 생성하는
데 사용된 실제 스키마이기 때문이다.
튜토리얼의 이후 섹션에서는 사용자 정의에 따라 제어되는 추가 변환 유형에 대해 설명한다.
|