Velocity란 무엇인가?
Velocity는 자바 기반의 템플릿 엔진이다.
Velocity는 웹 페이지 디자이너들이 자바 코드안에서 정의된 메소들에 접근하는 것을 도와준다. 이것은 웹 페이지 디자이너들이 자바 개발자들과 함께 Model-View-Controller(MVC) 아키텍쳐에 따른 웹 사이트를 각자의 영역에서 최선의 결과를 가져오도록 도와준다는 것을 의미한다.
Velocity는 웹 페이지로부터 자바 코드를 분리할 수 있고, 웹사이트를 계속 오랫동안 유지할 수 있으며, 자바 서버 페이지(JSP)의 실용적인 대안을 제공한다.
Velocity로 무엇을 할 수 있나?
당신이 진흙(Mud)을 판매하는 "온라인 진흙 가게"의 페이지 디자이너 이라고 가정해보자. 그리고 고객들은 다양한 종류와 많은 수량의 진흙을 주문하여 당신의 가게는 날로 번성하고 있다.
고객들은 사용자 이름과 패스워드를 입력하고 당신의 사이트에 로그인하여 자신이 이전에 주문했던 주문목록을 조회하고, 또 다른 진흙을 살 수도 있다. 모든 고객에 관한 정보는 당신의 데이타 베이스안에 저장되고 있다.
데이타 베이스에 따르면 특별히 설악산에서 만든 진흙이 매우 인기가 좋으며, '설악산 진흙'보다 인기가 덜 한 일본산 빨간 진흙도 정기적으로 일본에서 수입하고 있다.
그러던 어느날 당신은 왜 '설악산 진흙'에 흥미를 느끼는 수많은 고객들과의 특별한 거래를 찾는데 Velocity를 사용하지 않는 지에 대한 의문을 갖게된다.
Velocity는 웹페이지를 통한 당신의 온라인 방문객들에게 진흙 판매를 쉽게 해준다. 진흙 가게의 웹사이트 디자이너인 당신은 고객이 웹 사이트 로그인 후에 보게 될 특별한 웹 페이지를 원하고 있다.
명심하라. 당신은 개발자들이 데이터베이스에서 필요한 정보를 어떻게 추출하는지에 대해 걱정할 필요가 없다. 당신은 단지 그것이 올바르게 작동한다는 것만 기억하면 된다. 그렇게 되면 당신은 당신만의 페이지 디자인을 하게 되고, 개발자들은 그들만의 일을 하게 된다.
이런 결과로, 당신은 로그인한 사용자에게 나타낼 동적인 정보를 아래와 같이 velocity Template Language(VTL) 문장을 사용해 웹페이지에 넣을 수 있게 된다.
Hello, $customer.Name! <br>
$flogger.getPromotion( $mud )
$customer는 현재 로그인한 사용자에 대한 정보를 가지고 있고, $promotion은 로그인한 고객별로 데이타 베이스를 조회에 해당 고객에 맞는 추천 상품을 소개하는 일을 한다.
그래서 '설악산 진흙'을 오랫동안 구입해온 기록을 가진 고객이 로그인 했을 때 '설악산 진흙 현재 대폭 세일중!!' 이라는 메시지를 정면에서 보게 될 것이다.
Velocity의 힘과 유연함은 이렇게 VTL 레퍼런스와 함께 당신에게 제공된다.
VTL (Velocity Template Language)
VTL은 Velocity에서 템플릿 개발에 사용되는 언어로 레퍼런스(Reference), 디렉티브(Directive), 그리고 주석(Comment)으로 구성된다.
레퍼런스 |
${variable} |
컨텍스트에서 제공되는 변수에 대한 레퍼런스 |
${variable.property} |
속성에 대한 레퍼런스 |
${variable.method(args)} |
메소드 대한 레퍼런스 |
디렉티브 |
#set |
레퍼런스의 값을 설정 |
#if #elseif #else |
조건문 제어 |
#foreach |
반복문 제어 |
#include |
파싱되지 않는 로컬 파일 출력 |
#parse |
파싱되는 로컬 템플릿 출력 |
#stop |
템플릿 엔진의 동작 정지 |
#macro |
반복적으로 사용될 매크로 정의 |
주석 |
## |
한 줄짜리 주석 |
#* .... *# |
여러 줄에 걸친 주석 |
VTL은 위 표에서 정리한 것처럼 너무나 단순하기 때문에 흔히 성냥갑 표지에 다 적을 수 있을 만큼 작은 API라고 불려진다.
이는 뷰 작업을 개발자와 디자이너가 함께 진행해 나간다는 점을 감안하면 매우 바람직한 일이라고 볼 수 있다.
VTL을 통해 작성된 벨로시티 템플릿(vm 파일)은 다음과 같은 패턴을 통해 처리 된다.
// 벨로시티 템플릿 엔진의 초기화
// 초기화 설정은 velocity.properties 파일을 사용함
Velocity.init("velocity.properties");
// 자바 클래스와 템플릿간의 정보 전달에 사용할 컨텍스트 객체 생성
// 컨텍스트 객체는 해시 테이블을 상속받으므로 (키, 값)의 순서쌍 형태로 값을 저장
// 예) context.put("members", memberDAO.getMembers());
// 이렇게 해서 컨텍스트에 저장된 값들은 템플릿에서 레퍼런스를 이용해서 참조됨
// 컨텍스트에 담긴 값이 컬렉션일 경우 #foreach 디렉티브를 통해 하나씩 참조 가능
VelocityContext context = new VelocityContext();
Template template = null;
// 로직과 연결되어 사용될 템플릿 파일을 선택
template = Velocity.getTemplate("HelloWorld.vm");
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
// 템플릿과 컨텍스트를 렌더링해서 그 결과를 출력
if ( template != null)
template.merge(context, writer);
writer.flush();
writer.close();
|
템플릿 엔진이 동작하는 방식을 정리해 보면, 개발자는 사용자에게 보여 질 디자인을 위해 VTL을 이용한 템플릿 파일을 작성하고, 그 처리를 위한 코드를 개발한다.
자바 코드와 템플릿 간의 필요한 정보 전달은 컨텍스트 객체를 통해 이루어진다.
이러한 2개의 파일을 입력받아 벨로시티는 템플릿을 토대로 렌더링한 결과 페이지를 출력하게 되는 것이다.
템플릿에 어떤 내용이 담겨 있느냐에 따라 결과 페이지는 일반 텍스트가 될 수도 있고, HTML이나 SQL, PostScript, XML 등도 될 수 있다.
Velocticy 템플릿 언어 (VTL)
Velocticy 템플릿 언어(velocity Template Language : VTL)는 웹 페이지에서 동적인 내용을 구체화하기 가장 쉽고, 가장 간결하고 가장 확실한 방법을 제공하는 수단이다.
심지어 프로그래밍 해본 경험이 적거나 전혀 없는 페이지 개발자도 웹사이트에서 동적인 내용를 구체화 하는데 VTL을 금방 사용할 수 있다.
VTL은 웹사이트에서 동적인 내용을 끼워넣는데 필요한 레퍼런스를 사용한다. 레퍼런스의 타입은 variable이다.
Variables는 자바 코드에서 정의된 것에 대해 언급할 수 있는 레퍼런스의 한 타입 이거나, 또는 웹페이지 자체의 VTL 에서 지원하는 타입일 수 있다.
아래는 HTML 문서안에 삽입될 수 있는 VTL문의 예제다.
이 VTL문은 모든 VTL문처럼 #기호로 시작하고 set 명령어를 포함하고 있다. 온라인 고객이 당신의 웹 페이지를 요청할 때, Velocticy 템플릿 엔진은 당신의 웹 페이지에 명시된 모든 #기호를 검색한다. VTL을 시작할 #기호와 VTL에서 아무일도 않는 #기호들을 분리한다.
위 예제에 표시된 set 명령어는 둥근괄호 안에서 사용하며 특정 변수에 값를 부여하는 일을 한다. 변수는 좌변에, 변수의 값는 우변에 나열되며 좌변과 우변은 = 기호로 구분되어 진다.
위의 예에서, 변수는 '$a'이고 이 변수에 할당된 값은 'Velocity'이다. 이 변수는 다른 모든 레퍼런스들처럼 $기호로 시작한다. 값은 항상 인용부호 사이에 존재한다.
Velocticy는 문자열(String) 타입만 변수에 할당할 수 있기 때문에 다른 데이타 타입들과의 혼란은 없다.
정리해보면 아래와 같다.
레퍼런스는 $로 시작하고 어떤 것을 얻는데 사용된다.
명령어는 #로 시작하고 어떤 동작을 수행하는데 사용된다.
위 예제에서 #set은 변수에게 값을 할당하는데 사용되고 $a 변수는 템플릿에서 "velocity"를 출력하는데 사용되었다.
안녕 velocity
변수에 값을 할당했으면, 당신의 HTML 문서내 어디서나 변수를 레퍼런스할 수 있다. 다음의 예에서 $foo에 값이 할당되고 그 후 레퍼런스 되는 모습을 볼 수 있다.
#set ($ foo = "velocity" )
Hello #foo World!
위 문장은 결국 웹페이지에 "Hello Velocity World!"를 출력할 것이다.
VTL 명령어들을 포함한 문장들을 좀더 읽기 쉽게 만들기 위해, 굳이 그럴 필요는 없지만 가급적 VTL문장을 새로운 줄에서 시작하도록 권장한다.
set 명령어는 나중에 매우 세부적으로 다시 살펴보게 될 것이다.
주석 처리
주석은 velocity 템플릿 엔진에 의해 해석되지 않는 설명문이다. 주석은 당신의 오래된 기억을 상기시키거나, 당신의 VTL문이 무엇을 하고 있는지, 또는 당신이 찾는 다른 다른 목적들을 설명하기에 유용한 방법이다.
아래에 VTL의 주석의 예가 있다.
##This is a single line Comment.
한줄 주석문은 ##로 시작하고 라인의 끝에서 끝난다. 당신이 여러 줄의 주석을 사용한다면, 다수의 한줄 주석을 여러 줄에 쓸 필요는 없다.
문장이 #*로 시작하고 *#로 끝나는 Multi-line 주석이 이런 상황에 매우 유용하다.
#*
Thus begins a multi-line comment. Online visitors won't
see this text because the velocity Templating Engine will
ignore it.
*#
아래 나오는 세 번째 타입의 주석은 문서의 저자와 버전정보와 같은 메타정보를 저장하는데 사용될 수 있다.
#**
This is a VTL comment block and
may be used to store such information
as the document author and versioning
information:
@author
@version 5
*#
참조 (References)
VTL 에서 지원하는 세가지 타입의 레퍼런스가 있다.
변수
속성
메소드
개발자들은 디자이너가 VTL을 이용하는 템플릿에서 개발자가 설정한 레퍼런스를 정확히 사용하기 위해 레퍼런스의 특별한 이름을 지어주어야 한다.
레퍼런스로 오고 가는 모든 것은 문자열(String) 객체로 처리된다. 만약 $foo(Integer 타입이라 가정한다)를 표현하는 객체가 있다면 Velocity는 문자열 객체를 얻기위한 매소드로 .toString()을 요청 할 것이다.
변수 (Variables)
변수의 표기법은 VTL 식별자로부터 불려온 '$'기호로 시작된다. VTL 식별자는 알파벳 (a...z, or A...Z)으로 시작해야 하며 나머지는 아래의 문자들로 제한된다.
alphabetic (a .. z, A .. Z)
numeric (0 .. 9)
hyphen ("-")
underscore ("_")
여기 VTL안에서 유효한 변수 레퍼런스의 예가 몇 개 나온다.
$foo
$mudSlinger
$mud-slinger
$mud_slinger
$mudSlinger1
VTL 변수가 $foo로써 레퍼런스 될 때, 변수는 템플릿안의 set 명령어나 자바 코드로부터 값을 얻을 수 있다.
예를 들어 자바 변수 $foo가 "value bar"라는 값을 가지고 있다면, "value bar"는 웹페이지내에 정의된 모든 $foo를 대체 하게 된다.
만약 다음 문장이 웹페이지 내에 정의된다면....
결과는 이 명령어 하위에 나오는 모든 $foo를 "bar"로 대체할 것이다.
속성 (properties)
VTL 레퍼런스의 두번째 특징은 속성이다.
속성은 특유의 포맷을 가진다. 표기법은 점 기호(".")와 다른 VTL 식별자가 뒤에 따르는 '$' 기호로 구성된다. 아래는 VTL안의 유효한 속성 레퍼런스의 예이다.
$customer.Address
$purchase.Total
위 예제에서 $customer.Address는 두가지 의미을 가질 수 있다.
customer 객체의 멤버 변수 Address를 레퍼런스 한다.
customer 객체의 메소드 getAddress()를 레퍼런스 한다.
즉, $customer.getAddress()는 $customer.Address로 생략해서 사용할 수 있다. 웹 페이지가 요청될 때, Velocity는 이들 두 가능성을 이치에 맞는지 분별하여 적당한 값은 반환하게 된다.
메소드 (Method)
메소드는 자바코드 안에서 정의되며, 계산을 하거나 어떤 값을 얻는 것과 같이 유용한 무언가를 수행한다. 아래는 유효한 메소드 레퍼런스의 예이다.
$customer.getAddress()
$purchase.getTotal()
$page.setTitle( "My Home page" )
$person.setAttributes( ["Strange", "Weird", "Excited"] )
예제에서 나온 $customer.getAddress()와 $purchase.getTotal()은 앞선 예제에서 나온 $customer.Address과 $purchase.Total 레퍼런스의 결과와 정확히 일치한다.
그러나 $page.setTitle( "My Home page" )과 같이 메소드에서는 인자를 넘길 수 있다는 점이 속성과 다르다. 참고로 $person.setAttributes( "Strange", "Weird", "Excited"? )는 인자로 문자열 배열을 넘기고 있다.
형식적인 레퍼런스 표기법 (Formal Reference Notation)
앞서 나온 것은 레퍼런스에 대한 속기 표기법이었다. 이제 형식적 표기법에 대해 살펴보자.
${mudSlinger}
${customer.Address}
${purchase.getTotal()}
거의 모든 경우에 당신은 레퍼런스를 위하여 앞서 나온 속기 표기법을 사용할 것이지만, 어떤 경우에는 형식적 표기법이 정확한 프로세스를 위하여 필요할 수도 있다.
만약 당신이 $vice라는 변수를 사용하는데 이 변수 바로 뒤에 "fly"라는 문장이 뒤따른다고 생각해보자. 속기 표기법을 사용할 경우의 다음 예제를 살펴보면....
예제에 나온 $vicefly라는 변수는 우리가 희망한 변수가 아니다. 이 결과로 Velocity는 $vice가 아닌 $vicefly를 변수로 가정해서 처리하게 된다. 결국 화면에는 우리가 예상하지 못한 $vicefly가 그대로 표시될 것이다.
아래 형식적 표기법을 통해 이 문제를 해결할 수 있다.
이제 Velocity는 $vicefly가 아닌 $vice가 변수라는 것을 알게 된다. 기억하자. 형식적 표기법은 레퍼런스들이 템플릿 안에서 텍스트에 직접 붙어 있을 때 유용하게 사용된다.
숨겨진 레퍼런스 표기법 (Quiet Reference Notation)
Velocity는 사전에 정의되지 않은 변수를 만나게 되면, 변수값을 그대로 화면에 출력하게 된다.
다음의 예제를 살펴보자.
<input type="text" name="email" value="$email" />
위에 나온 $email이 사전에 정의되지 않았을 경우 화면에 나오는 텍스트필드의 초기값에는 $email이라는 값이 그대로 표시될 것이다. 이것은 우리가 원하는 결과가 아니다.
이제 다음처럼 예제를 바꿔보자.
<input type="text" name="email" value="$!email"/>
이제 화면에는 텍스트필드의 값이 빈 공백이 출력될 것이다. 형식적 표기법을 사용한 다음 예제도 같은 결과를 가져온다.
<input type="text" name="email" value="$!{email}"/>
값이 설정되지 않은 변수를 화면에 출력하지 않을 경우 숨겨진 레퍼런스 표기법은 아주 유용하다.
Dollar($)표시의 경우는 어떻게 하는가?
만약 당신이 웹 페이지 내에서 "나는 농부의 가게에서 $2.50를 주고 4파운드의 감자를 샀습니다.!" 라는 문자열을 표시해야 한다고 가정하자.
VTL에서는 앞서 보았듯이 $와 # 같은 특별한 캐릭터들을 사용하고 있어서 언뜻 보면 위 문자열이 제대로 표시가 안될 수도 있을 것 같다.
그러나 VTL 식별자는 항상 대문자나 소문자의 영문자로 시작하기 때문에 위 문장은 아무런 문제가 없다.
escape 캐릭터의 사용
만약 당신이 화면에 $email을 출력해야 한다고 가정하자. 그러나 이미 $email은 값이 설정된 상태라고 하자. 그러면 화면에는 당신이 원하지 않는 $email에 대한 설정값이 화면에 표시될 것이다.
아래 예제가 있다.
#set ( $email = "foo" )
$email
이 경우 화면에는 foo라는 문자열이 출력되어 당신이 원하는 $email이라는 문자는 출력할 수 없게 된다.
이경우 다음과 같이 escape 캐릭터를 사용하면 된다.
#set( $email = "foo" )
$email
\$email
\\$email
\\\$email
결과는 아래와 같이 나올 것이다.
만약 $email이 사전에 정의되지 않았다고 가정해보면 어떻게 될까? 결과는 아래와 같을 것이다.
$email
\$email
\\$email
\\\$email
Velocity 문법(지시어)
앞서 배운 레퍼런스(변수)는 웹 디자이너들이 웹 페이지에 동적인 내용을 생성할 수 있도록 도와준다.
반면에 이번에 살펴볼 디렉티브(명령어)는 웹 디자이너들이 실제적으로 웹사이트의 뷰와 컨텐츠를 관리하도록 도와준다.
#set
명령어 #set은 레퍼런스의 값을 설정할 때 사용된다. 값은 변수 레퍼런스나 속성 레퍼런스 둘 모두에 할당될 수 있고, 이것은 아래에 보이는 것처럼 괄호 안에서 명명된다.
#set( $primate = "monkey" )
#set( $customer.Behavior = $primate )
괄호안의 좌측에 위치하는 변수(이하 LHS로 표기, 우측 변수는 RHS로 표기)는 아래에 나오는 레퍼런스 또는 속성 타입이어야 한다.
Variable reference
String literal
Property reference
Method reference
Number literal
ArrayList
아래는 위의 각 타입들에 예제이다.
#set( $monkey = $bill ) ## variable reference
#set( $monkey.Friend = "monica" ) ## string literal
#set( $monkey.Blame = $whitehouse.Leak ) ## property reference
#set( $monkey.Plan = $spindoctor.weave($web) ) ## method reference
#set( $monkey.Number = 123 ) ## number literal
#set( $monkey.Say = ["Not", $my, "fault"] ) ## ArrayList
마지막 줄에서 대괄호로 표시된 element는 ArrayList class의 요소를 정의한다. 그래서 예를 들면, 당신은 $monkey.Say.get(0)을 이용해서 첫번째 배열요소(여기서는 "Not")에 접근할 수 있다.
또한 간단한 수학적 표현도 가능하다.
#set( $value = $foo + 1 )
#set( $value = $bar - 1 )
#set( $value = $foo * $bar )
#set( $value = $foo / $bar )
null 체크
만약 RHS가 속성 또는 그 값이 null인 메소드 레퍼런스 라면, LHS로 할당되지 않을 것이다. 문맥에서 현재의 레퍼런스를 이 매커니즘으로 제거하는 것은 불가능하다. 이런 점은 Velocity의 초보자들이 흔히 혼동 하는 실수다.
예를 들면....
#set( $result = $query.criteria("name") )
The result of the first query is $result
#set( $result = $query.criteria("address") )
The result of the second query is $result
위에서 만약 $query.criteria("name")이 문자열 "bill"로 return되고, $query.criteria("address")이 null로 return되면, 위 VTL의 결과는 아래와 같이 출력될 것이다.
The result of the first query is bill
The result of the second query is bill
이것은 property 또는 매소드 레퍼런스를 거쳐 #set 레퍼런스를 시도한 후,
1. foreach 반복문을 만드는 초보자들을 혼동케하는 경향이 있다.
이때는 아래와 같이 null이 의심가는 레퍼런스를 #if 명령어를 가지고 시험한다.
#set( $criteria = ["name", "address"] )
#foreach( $criterion in $criteria )
#set( $result = $query.criteria($criterion) )
#if( $result )
Query was successful
#end
#end
위의 예에서, query가 성공하였는지 결정하는데 있어 $result의 결과값에 의존하는 것은 현명한 결정이 아니다. $result가 위에서 처럼 조건절 이전에 #set을 통해 설정 되어진 후에는 $result를 null로 다시 되돌릴 수 없다. (#if와 #foreach 명령어의 세부사항들이 이 문서의 뒤에 다루어질 것이다.)
이에대한 하나의 해결책은 $reult를 false로 사전에 설정하는 방법이다. 그런 다음 $query.criteria( ) 호출이 실패하면, 당신은 $result가 null임을 체크할 수 있게 된다.
#set( $criteria = ["name", "address"] )
#foreach( $criterion in $criteria )
#set( $result = false )
#set( $result = $query.criteria($criterion) )
#if( $result )
Query was successful
#end
#end
몇몇의 다른 Velocity 명령어들과 다르게, #set 디렉티브는 #end 문을 가지지 않는다는 점도 참고하라.
문자열 (String literals)
명령어 #set을 사용할 때 큰 따옴표로 지정된 문자열은 아래 예제처럼 파싱되어 출력된다.
#set( $directoryRoot = "www" )
#set( $templateName = "index.vm" )
#set( $template = "$directoryRoot/$templateName" )
$template
당연하겠지만, 결과는 아래와 같다.
그러나 문자열이 작은 따옴표일 때는 파싱되지 않는다.
#set( $foo = "bar" )
$foo
#set( $blargh = '$foo' )
$blargh
결과는 아래와 같다.
Velocity에서 파싱되지 않은 텍스트를 출력하기 위해 위에서 처럼 작은 따옴표를 사용할 수 있다.
이 설정은 velocity.properties 설정파일에서 stringliterals.interpolate=false를 설정함에 따라 기본설정을 바꿀 수도 있다.
조건절 (If / ElseIf / Else)
Velocity에서 #if 명령어는 문장이 참인 조건에서 웹페이지가 생성될 때 포함될 문자열을 지정할 수 있다.
#if( $foo )
Velocity!
#end
위에서 #if 명령어는 변수 $foo가 참인지 결정하기 위해 사용되며 아래 2가지 상황일 수 있다.
$foo는 참인 값을 갖는 boolean(true/false)이다.
$foo는 null이 아니다.
Velocity 문맥은 항상 객체(Object)로써 유지된다는 것을 기억하라. 그래서 우리가 boolean이라고 말할 때, 그것은 Boolean(class)으로 표현될 것이다.
예제에서 #if 와 #end 사이에 들어있는 content가 참이면 결과가 나올 것이다. 여기서는 $foo가 참이면, "Velocity!"가 출력될 것이고, 반대로, $foo가 null값이거나 false이면 아무런 내용도 표시되지 않을 것이다.
명령어 #elseif 또는 #else는 #if 요소와 함께 사용될 수 있다. 다음의 예제에서, $foo가 15의 값를 갖고 $bar가 6의 값을 갖는다고 가정해보자.
#if( $foo > 10 )
Go North
#elseif( $foo == 10 )
Go East
#elseif( $bar == 6 )
Go South
#else
Go West
#end
위 예제에서 $foo가 10보다 크면 첫번째 두 비교들이 실패하게 된다. 다음 $bar가 6과 비교되어 참이 되고, 그래서 결과는 "Go South"가 된다.
※ 참고 : '=='로 비교되는 변수는 같은 Object 타입이어야 한다. 그렇지 않으면 결과는 항상 false가 될 것이다.
논리 연산
Velocity는 논리 연산자 AND, OR, NOT 을 지원한다. 아래 예제가 나온다.
## logical AND
#if( $foo && $bar )
This AND that
#end
위에서 #if() 명령은 단지 $foo와 $bar가 참인지만 평가할 것이다. 만약 $foo 가 false라면, 결과는 false가 되며 $bar는 평가되지 않을 것이다. 만약 $foo가 참이라면, Velocity는 $bar의 값을 체크하게 된다. 만약 $bar도 참이면, 결과는 true가 되고 화면에는 "This And that"이 나타날 것이다. $bar가 false라면, 그때 결과는 false이므로 결과는 없을 것입니다.
논리 연산자 OR도 같은 방법으로 작동한다. 아래 예제가 나온다.
## logical OR
#if( $foo || $bar )
This OR that
#end
OR 연산에 대해 별도의 설명은 하지 않는다. 아래는 부정연산의 예제다.
##logical NOT
#if( !$foo )
NOT that
#end
여기서, $foo가 참이라면, 그 때 !$foo는 false로 평가하고, 아무런 출력이 없게된다. 만약에 $foo가 false라면, 그 때 !$foo는 참으로 평가되어 화면에서 "NOT that"이 출력될 것이다.
※ 주의 : 이것을 완전하게 앞서 배운 숨긴(quiet) 레퍼런스와 혼동하지 않도록 조심하라. 숨긴 레퍼런스의 형식은 $!foo 이다.
반복문 (Loops)
명령어 #foreach는 반복문을 사용할 때 필요하다.
<ul>
#foreach( $product in $allProducts )
<li>$product</li>
#end
</ul>
위 예제에서 #foreach loop는 $allProducts 컬렉션(object) List 안의 전체 product에 대해 반복문을 수행하도록 한다. 위 loop를 통해서 $allProducts 내에 담긴 Object는 $product 변수로써 레퍼런스하게 된다.
위에 나온 $allProducts 변수에 사용되는 타입은 아래와 같다.
Vector
Hashtable
Array
이제 $allProduct가 Hashtable이라고 가정하자.
만약 당신이 Hashtable에 담긴 키값을 검색해야 한다면, 아래와 같은 코드를 사용하면 된다.
<ul>
#foreach( $key in $allProducts.keySet() )
<li>Key : $key -> value : $allProducts.get($key)</li>
#end
</ul>
또한 Velocity는 당신이 반복문의 횟수(loop counter)를 얻을 수 있는 간단한 방법을 제공한다.
<table>
#foreach( $customer in $customerList )
<tr><td>$velocityCount</td><td>$customer.Name</td></tr>
#end
</table>
velocity.properties 파일에 명시되어있는, loop counter 변수 레퍼런스를 위한 기본값은 $velocityCount이다. 기본값에 따라 카운터가 1에서 시작하지만, 이것은 velocity.properties 파일안에서 0 이나 1 둘 모두로 설정할 수 있다. 아래는 velocity.properties 파일에 지정된 관련 설정 내용이다.
# Default name of the loop counter
# variable reference.
directive.foreach.counter.name = velocityCount
# Default starting value of the loop
# counter variable reference.
directive.foreach.counter.initial.value = 1
인클루드 (Include)
명령어 #include는 템플릿에 local 파일을 끼워넣을 수 있도록 해준다. local file은 #include 명령이 정의된 위치에 정확히 삽입된다.
아래는 #include시 참고할 사항이다.
파일의 내용은 템플릿 엔진을 통해 파싱되지 않는다.
안전상의 이유로, 파일은 오직 TEMPLATE ROOT 아래에만 위치해야 한다.
위에서 처럼 #include 명령이 요청한 파일은 큰 따옴표로 지정된다. 만약 하나 이상의 파일이 포함된다면, 콤마(,)로 구분되어야 한다.
#include( "one.gif", "two.txt", "three.htm" )
포함되는 파일은 파일 이름 대신에 변수를 사용할 수도 있다. 아래 파일이름과 변수 모두를 지정하는 에제가 나온다.
#include( "greetings.txt", $seasonalstock )
파싱 (Parse)
명령어 #parse는 템플릿 디자이너가 VTL이 지정되어 있는 동적인 템플릿을 포함하는 local file을 끼워넣도록 해준다. Velocity는 이 파일에 명명된 VTL을 분석하고 평가된 결과를 #parse 명령이 정의된 위치에 정확히 삽입한다.
아래는 #parse시 참고할 사항이다.
파일의 내용은 템플릿 엔진을 통해 파싱된다.
안전상의 이유로, 파일은 오직 TEMPLATE ROOT 아래에만 위치해야 한다.
앞서 나온 #include 명령처럼, #parse 역시 변수를 취할 수 있다. 주의해야 할 것은 #include 명령과 다르게, #parse는 단지 하나의 파일만을 취할 수 있다는 점이다.
Velocity는 또한 #parse 문장을 포함하고 있는 템플릿을 #parse 인자로 가질 수 있다.
파싱되는 파일의 제한을 위한 최적의 기본값은 10이며 velocity.properties 파일내에 directive.maxdepth=10 이라는 설정을 통해 이를 제어할 수 있다.
아래 예제를 보자.
1) default.vm
Count down.
#set( $count = 8 )
#parse( "parsefoo.vm" )
All done with dofoo.vm!
2) parsefoo.vm
#set( $count = $count - 1 )
#if( $count > 0 )
#parse( "parsefoo.vm" )
#else
All done with parsefoo.vm!
#end
위 예제에 따라 default.vm에서 $count는 8부터 count down하면서 parsefoo.vm을 파싱하게 된다.
그리고 count가 0에 도달할 때, Velocity는 "All done with parsefoo.vm!" 이라는 메세지를 표시하고, 이 시점에서, 제어권은 default.vm으로 돌아가 "All done with dofoo.vm!" 이라는 메시지를 출력할 것이다.
중지 (Stop)
명령어 #stop은 템플릿 엔진의 실행과 return을 강제로 멈추도록 하는데 사용한다.
보통 디버깅(debugging) 목적으로 사용된다.
다음으로 Velocity의 나머지 특징과 기타 살펴볼 내용들에 대해 설명을 계속한다.
수학적인 기능 (Math)
Velocity #set 명령어를 가지고 템플릿들에서 사용될 수 있는 수학적인 기능들을 제공한다. 다음의 식들은 각각 덧셈, 뺄셈, 곱샘 그리고 나눗셈에 대한 예제이다.
#set( $foo = $bar + 3 )
#set( $foo = $bar - 4 )
#set( $foo = $bar * 6 )
#set( $foo = $bar / 2 )
#set( $foo = $bar % 5 )
수학적인 기능은 오직 정수만 (.... -2, -1, 0, 1, 2 ....)만 가능하다는 점에 주의하라.
범위 연산 (Range Operator)
범위 연산은 #set과 #foreach 구문 사이에서 이용될 수 있다. 범위연산자는 다음의 구조를 가진다.
위 예제에서 n과 m은 모두 정수여야 한다. m이 큰지 n이 작은지는 문제가 되지 않는다. n과 m사이에서 반복문이 순환될 것이기 때문이다.
다음은 범위연산의 사용 예제를 보여주고 있다.
First example:
#foreach( $foo in [1..5] )
$foo
#end
Second example:
#foreach( $bar in [2..-2] )
$bar
#end
Third example:
#set( $arr = [0..1] )
#foreach( $i in $arr )
$i
#end
Fourth example:
[1..3]
결과는 아래와 같다.
First example:
1 2 3 4 5
Second example:
2 1 0 -1 -2
Third example:
0 1
Fourth example:
[1..3]
Velocity 문법(매크로)
매크로
명령어 #macro는 VTL 템플릿에서 반복되는 문장을 정의하는데 사용된다.
매크로는 간단하고 복잡한 시나리오 모두에서 매우 유용하게 쓰인다. 매크로는 keystrokes를 저장하고 typographic error를 최소화할 목적으로 만들어졌다.
#macro( d )
<tr><td></td></tr>
#end
위 예제에서 정의되는 Velocimacro는 d 이다. 이 매크로는 아래와 같은 방법으로 호출된다.
이 템플릿이 불려질 때, Velocity는 아래와 같은 결과로 #d()를 대체하게 된다.
또한 Velocimacro는 인자의 수를 취할 수도 있다. 심지어 앞선 예제에서 보여진 zero argument 조차 하나의 옵션이다.
그러나 Velocimacro가 불려질 때, 그것은 정의된 수만큼의 argument와 함께 불려야 합니다. 많은 Velocimacros는 위에서 정의된 하나보다 더 많이 관련됩니다. 여기 색과 array, 두 개의 argument를 취한Velocimacro가 있습니다.
#macro( tablerows $color $somelist )
#foreach( $something in $somelist )
<tr><td bgcolor=$color>$something</td></tr>
#end
#end
이 예제에서 정의되는 매크로, #tablerow는 두 개의 argument를 취한다. 첫번째 argument는 $color를 대체하고 두번째 argument는 $somelist를 대체하게 된다.
VTL 템플릿 안으로 넣어질 수 있는 것은 모두 매크로 안으로 지정될 수 있다. 위에서 #tablerows 매크로는 foreach 명령어를 사용했다. #tablerows 매크로 정의를 보면 두개의 #end 명령이 있다. 예상하겠지만 첫번째는 #foreach에 속하고, 두번째는 매크로 정의를 마치는데 사용되는 것이다.
아래에 위에서 정의한 #tablerow 매크로를 사용하는 예제가 나온다.
#set( $greatlakes = ["Superior","Michigan","Huron","Erie","Ontario"] )
#set( $color = "blue" )
<table>
#tablerows( $color $greatlakes )
</table>
변수 $greatlakes가 $somelist를 대체함에 유의하면서 아래 결과를 살펴보자.
<table>
<tr><td bgcolor="blue">Superior</td></tr>
<tr><td bgcolor="blue">Michigan</td></tr>
<tr><td bgcolor="blue">Huron</td></tr>
<tr><td bgcolor="blue">Erie</td></tr>
<tr><td bgcolor="blue">Ontario</td></tr>
</table>
매크로는 하나의 Velocity 템플릿 안에서 inline으로 정의될 수 있으나 이렇게 정의한 매크로는 다른 템플릿에서는 사용할 수 없다. 매크로가 모든 템플릿들에 공유될 수 있도록 정의하기 위해서 velocity.properties 설정파일 내에 template library (velocimacro.library)를 지정하도록 한다.
공유되는 매크로의 이점은 많은 템플릿 들에서 매크로를 재정의할 필요를 줄여 작업량을 줄이고, 오류가 발생할 수 있는 기회를 줄인다는 점이다.
매크로 인자 (Arguments)
매크로에 정의되는 argument는 다음의 VTL 요소들을 모두 취할 수 있다.
Reference : anything that starts with '$'
String literal : something like "$foo" or 'hello'
Number literal : 1, 2 etc
IntegerRange : [ 1..2] or [$foo .. $bar]
ObjectArray : [ "a", "b", "c"]
boolean value true
boolean value false
velocity.properties에서의 매크로 관련 설정
velocimacro.library - 콤마(,)로 구분된 모든 템플릿 라이브러리 목록. VM_global_library.vm.설정된 template path가 Velocimacro library를 찾는데 사용됩니다.
velocimacro.permissions.allow.inline - true(기본값)/false. 이 속성은 매크로가 표준 템플릿 내에서 inline으로 정의될 수 있는지를 결정한다.
velocimacro.permissions.allow.inline.to.replace.global - true/false(기본값). 만약 템플릿 내에서 inline으로 정의된 매크로가 velocimacro.library에 설정된 라이브러리의 매크로를 대체할 지를 설정한다. 만약 false라면 템플릿에 inline으로 정의된 매크로가 컨테이너 구동시 로드된 라이브러리의 매크로를 교체하는 것을 막는다.
velocimacro.permissions.allow.inline.local.scope - true/false(기본값). 이 속성은 inline으로 정의된 매크로가 정의하는 template에 대해 'visible'인지 아닌지를 조절한다. 이 속성을 true로 하면, 템플릿은 단지 정의되는 템플릿에 한해서만 유용한 inline VM을 정의할 수 있게 된다.
velocimacro.context.localscope - true/false(기본값). true라면 매크로를 통한 #set() 명령으로 문맥의 모든 modification이 매크로에 대해 'local'로 고려되며 영구적으로 영향을 끼치지 않게 된다.
velocimacro.library.autoreload - true/false(기본값). 이 속성은 자동 로드되는 매크로를 조절한다. true로 설정되면 요청된 매크로에 대한 원본 매크로 설정파일의 변화를 체크하여 필요시 해당 매크로를 리로드한다. 이것은 컨테이너 재기동 없이 매크로 라이브러리를 바꾸고 테스트 하도록 해준다. 이 모드는 오직 caching이 resource 안에서 off일때만 작동한다. (예 : file.resource.loader.cache = false). 이 속성은 운영단계가 아닌 개발단계에서만 사용하도록 권장한다.
매크로의 인자로서 다른 매크로나 디렉티브(명령어)를 이용할 수 있나?
다음 처럼 말이다.
#center( #bold("hello") )
그럴 수 없다. 이 명령은 타당하지 않다.
그러나 당신은 아래 예제처럼 이 문제를 우회하여 해결할 수 있다.
#set($stuff = "#bold('hello')" )
#center( $stuff )
#center( "#bold( 'hello' )" )
결과는 동일하다.
다른 예제를 살펴보자.
#macro( inner $foo )
inner : $foo
#end
#macro( outer $foo )
#set($bar = "outerlala")
outer : $foo
#end
#set($bar = 'calltimelala')
#outer( "#inner($bar)" )
결과는 아래와 같다.
outer : inner : outerlala
어떻게 문자열을 연결해야 하나?
당신은 단순히 연결해야 할 문자열들을 같이 놓으면 된다. 아래 예제를 통해 살펴보라.
#set( $size = "Big" )
#set( $name = "Ben" )
The clock is $size$name.
#set( $size = "Big" )
#set( $name = "Ben" )
#set($clock = "$size$name" )
The clock is $clock.
#set( $size = "Big" )
#set( $name = "Ben" )
#set($clock = "${size}$name" )
The clock is $clock.
결과는 모두 동일하게 "The clock is BigBen'이 된다.
프레임워크 내장 객체의 사용
TDF2에서 지원하는 Velocity 템플릿에서 사용가능한 내장 객체 레퍼런스를 소개한다.
$actionInstanceVariable
$req - 현재 HttpServletRequest
$res - 현재 HttpServletResponse
$stack - 현재 OgnlValueStack
$ognl - OgnlTool
$webwork - WebWorkUtil 인스탄스
$action - 현재 WebWork action
$taglib (또는 이와 유사한 것) - Velocity 매크로를 통한 JSP tag 라이브러리에 접근 가능
$actionInstanceVariable
당신이 만든 Action 클래스의 멤버변수들에 대해 $actionInstanceVariableName 으로써 Velocity 템플릿이 접근할 수 있도록 해준다.
만약 당신의 Action 클래스에서 아래와 같은 멤버변수를 가진다고 하자.
public class ProcessEditTableRowAction extends ActionSupport {
private String fooString;
....
public String getFooString() {
return fooString;
}
....
이 경우, Velocity 템플릿에서 아래와 같은 레퍼런스를 통해 위 멤버변수의 값을 꺼낼 수 있다.
$req
당신이 사용하는 서블릿 환경(Tomcat, Resin, etc....)에서 생성되어 관리되는 현재의 HttpServletRequest 인스탄스
$res
당신이 사용하는 서블릿 환경(Tomcat, Resin, etc....)에서 생성되어 관리되는 현재의 HttpServletResponse 인스탄스
$stack
com.opensymphony.xwork.util.OgnlValueStack 인스탄스
$ognl
com.opensymphony.webwork.views.jsp.ui.OgnlTool 인스탄스
Jason/Patrick에 의하면, OgnlTool은 static한 클래스 멤버에 접근할 수 있는 아주 멋진 Tool이다. Velocity에서는 Context에 설정되지 않은 클래스 변수나 메소드에 접근하는 기능을 제공하지 않는다. 그래서 당신은 템플릿 내에서 다음과 같은 메소드를 호출할 수 없다.
그러나 $ognl을 이용하면 아래와 같은 구문이 가능해진다.
$ognl.findValue("@java.lang.Math@random()")
$webwork
com.opensymphony.webwork.util.WebWorkUtil 인스탄스
Mathew에 따르면 $webwork는 다른 객체를 인스탄스화(초기화)할 수 있는 기능을 제공한다. Velocity에서는 Context에 설정되지 않은 객체를 초기화하는 방법을 제공하지 않기 때문에 이 기능은 매우 유용하다.
객체를 초기화하기 위해 당신은 템플릿 내에서 다음과 같은 문장을 사용할 수 있다.
#set($object = $webwork.bean("com.foo.ClassName"))
$action
현재 템플릿을 호출한 Action 컨텍스트의 인스탄스
$taglib
TDF2에서는 웹 디자이너와의 혼선을 피하기 위해 taglib 사용을 권장하지 않는다. 대부분의 지원 태그가 폼(form)과 관련된 내용이고, 이것의 사용으로 많은 이점을 제공한다고 보기 어렵기 때문에 TDF2에서는 가급적 익숙한 HTML 표준 폼 태그를 이용하기를 권장한다.
$taglib에 대한 자세한 정보를 알고 싶다면 http://wiki.opensymphony.com/display/WW/UI+Tags?showChildren=true#children 를 방문하라.
아파치 벨로시티를 사용한 템플릿 기반의 자동 코드 생성 - 1장
05/05/2004
상업적으로나 오픈소스로나 여러가지 형태로 제공되는 코드 생성기는 템플릿과 템플릿 엔진을 사용하고 있다. 이 기사에서 필자는 템플릿 기반의 코드 생성과 템플릿과 변환 방법, 그리고 그런 것들을 사용함으로써 얻게 될 막대한 이득에 대해 설명해보고자 한다. 그리고 벨로시티를 사용한 간단한 자바 코드 생성기를 만들어 볼 것이다. 그 코드 생성기는 클래스를 XML로 표현한 XML 파일을 입력으로 받아들여 그 XML이 명시하고 있는 자바 코드를 생성하는 기능을 가지게 될 것이다. 생성과정은 결과물 코드로 뽑아낼 언어의 문법(신택스)을 내부에 포함하고 있는 각각의 템플릿들에 의해 결정된다. 그리고 또한 다른 코드를 생성해내기 위하여 템플릿을 어떻게 사용하는지도 주목해서 보라.
템플릿과 변환
템플릿을 기반으로 한 변환을 소프트웨어 개발에 있어 엄청나게 광범위하게 사용된다. 많은 툴들이 문서의 포맷을 변환시키기 위하여 사용하기도 한다. XML 파일을 다른 특정 포맷으로 변환시키기 위하여 XSL 템플릿을 기반으로 하여 XSLT를 사용하는 것이 그 한 예라고 할 수 있다. 그림1은 템플릿 변환 과정에 있어 관계있는 4개의 컴포넌트를 보여주고 있다. 그것들은 다음과 같다:
데이터 모델: 특정한 구조를 이루며 데이터를 포함하고 있다. 이 데이터는 변환되어야 할 것들이며 변환 과정 중에 이용된다.
템플릿: 데이터 모델을 특정한 결과 코드로 포맷팅한다. 물론 그것을 위해서 템플릿은 그 내부에 데이터 모델의 레퍼런스를 가지고 사용하게 된다.
템플릿 엔진: 변환 과정을 직접 수행하는 어플리케이션이다. 입력으로 데이터 모델과 템플릿을 받아들여 템플릿에 적힌 데이터 모델의 레퍼런스를 실제 데이터 모델의 값으로 치환하여 템플릿을 통한 변환 과정을 수행한다.
결과물: 위 변환 과정을 거친 뒤 나오는 결과물
그림 1.템플릿 기반 변환
다음의 데이터 모델의 예를 한번 보자.:
#person.txt
$name= 길동
$surname= 홍
|
홍길동씨의 이름과 성을 포함하고 있는 텍스트 파일이다. 우리는 다음과 같은 형태의 템플릿을 통해 이것을 변환시키고 싶어졌다.:
안녕하시오. 난 $surname 가고 이름은 $name 이오. 템플릿또한 텍스트 파일이다. 두개의 데이터 모델에 대한 레퍼런스를 포함하고 있는데 $name과 $surname이란 레퍼런스가 그것이다.
만약 템플릿 엔진 어플리케이션 이름이 transform이라면 다음과 같이 데이터 모델과 위의 템플릿을 넘겨주어 다음과 같이 변환을 수행할 수가 있다.:
> transform person.txt person.template
결과물은:
|
안녕하시오. 난 홍 가고 이름은 길동 이오. 템플릿 엔진은 데이터 모델로부터 얻어낸 “홍길동” 이라는 값을 $name 과 $surname 의 레이블을 치환하였다.
한마디: 템플릿 기반의 변환에서 가장 중요한 점은 어떠한 결과물을 뽑아내는 프로그램 그 자체를 건드리지 않고도 결과물의 포맷을 맘대로 바꿀 수 있다는 점이다. 결과물을 바꾸고 싶으면 단지 해야 할 일은 템플릿을 조금 수정하는 것 뿐이다. 여기 다음의 템플릿을 한번 보라.:
#person2.template
***********************
이름 = $name
성 = $surname
***********************
|
여기서는 위에서 사용했던 똑 같은 데이터 모델과 새로운 템플릿 사용하여 변환 과정을 수행해 보겠다.:
> transform person.txt person2.template
|
결과로는:
***********************
이름 = 길동
성 = 홍
***********************
|
위에서 보았던 것과 같이 단지 템플릿만 수정함으로 결과를 조작할 수가 있다.
템플릿 기반 코드 생성
코드 생성과정또한 분명하게 위에서와 같은 변환 과정이다. 데이터 모델은 뽑아낼 코드의 중심이 되는 엔티티의 정보를 담고 있는 것이라고 볼 수가 있고 템플릿을 뽑아낼 결과물 코드의 언어 문법을 표현한다고 볼 수가 있다.
또다른 작은 예로는 언어 독립적인 코드 생성을 보여준다. 여기 예를 보라.:
#student.txt
$name=Student
$base=Person
|
이 파일은 클래스 이름과 클래스의 베이스 클래스(부모 클래스)를 명시하고 있다. 이것을 기반으로 자바 코드 즉 Student 클래스를 뽑고 싶으면 다음과 같은 템플릿을 사용하면 된다.:
#javaclass.template
public class $name extends $base {
}
|
위의 데이터 모델과 템플릿을 입력받은 템플릿 엔진은 다음과 같은 변환 결과를 출력할 것이다.:
public class Student extends Person {
}
|
Student 클래스의 정의 코드다. 우리는 데이터 모델과 템플릿으로부터 코드 생성을 시작할 수가 있다.
인터페이스를 만들려면 템플릿을 조금만 바꾸면 된다.:
#javainterface.template
public interface $name implements $base {
}
|
위의 템플릿을 템플릿 엔진에다 넣으면 다음과 같은 결과가 출력된다.
public interface Student implements Person {
}
|
엔진은 Student를 클래스가 아닌 인터페이스로도 뽑게 해 준다. 여기에서의 장점은 데이터 모델이나 템플릿 엔진 둘다 건드릴 필요 없이 이 작업을 했다는 것이다.
그리고 언어 독립성을 보여주기 위해 C++코드를 만들 수 있게 템플릿을 작성해 보자.:
#cpp.template
class $name : public $base
{
}
|
결과물은:
class Student : public Person
{
}
|
C++ 에서의 Student클래스 정의가 나왔다. 물론 데이터 모델이나 엔진을 고치지 않았다. 우리는 똑 같은 것을 기반으로 템플릿만 바꿔서 다른 언어의 코드를 만들어 낸 것이다! 이건 주목할만한 것인데 잘 디자인되고 템플릿을 기반으로 하는 코드 생성기는 단지 템플릿만 바꿔도 다른 언어의 코드를 생성할 수 있도록 해 준다. 그래서 우리는 언어에 종속적이지 않고 잘 디자인된 객체 모델을 만들어야 하는 것이다.
아파치 벨로시티
이전 예제에서 $로 시작되는 문자열을 데이터 모델로 치환시켜주는 가상의 간단한 템플릿 엔진을 가정하고 설명을 해왔다. 이제 진짜 코드를 생성하기 위해서 진짜 템플릿 엔진을 사용해 보도록 하자.
아파치 벨로시티 템플릿 엔진은 자카르타 오픈 소스 툴이다. VTL(벨로시티 템플릿 랭귀지) 라고 불리는 간단한 템플릿 언어를 기반으로 동작한다. 벨로시티는 자바를 기반으로 동작하도록 제작되었으며 벨로시티 컨텍스트에 연결된 일반 자바 클래스를 데이터 모델로써 사용한다. 변환 과정은 템플릿과 컨텍스트를 입력으로 받아 템플릿에 명시된 포맷대로의 결과물을 내는 것으로 이루어진다. 변환 과정 중에 템플릿의 레이블들은 컨텍스트에 포함된 실제 데이터로 치환된다.
나는 샘플 코드를 J2SE 1.4.2 와 벨로시티 1.4 를 기반으로 작성했고 테스트도 해 보았다. 이를 사용하기 위해서는 클래스패스에 두 개의 jar 파일 - velocity-1.4-rc1.jar and velocity-dep-1.4-rc1.jar. – 을 포함해야 한다.
실전에서의 벨로시티
다음 예제는 벨로시티를 사용한 간단한 코드 생성기인데 멤버 변수와 접근자 메소드(get/set 메소드) 를 포함한 간단한 자바 클래스를 생성하는 기능을 갖고 있다. 데이터 모델은 클래스들의 이름과 각 클래스들의 애트리뷰트 이름을 명시하고 있는 XML 파일로써 제공된다 여기 입력 데이터 모델 파일을 한번 보라.:
<?xml version="1.0" encoding="UTF-8"?>
<!-- order.xml --!>
<Content>
<Class name="Customer">
<Attribute name="code" type="int"/>
<Attribute name="description" type="String"/>
</Class>
<Class name="Order">
<Attribute name="number" type="int"/>
<Attribute name="date" type="Date"/>
<Attribute name="customer" type="Customer"/>
</Class>
</Content>
|
XML 파일 구조는 뭐 말할 것도 없이 쉽다. 엘레먼트는 클래스들 의미하며 요소는 클래스의 데이터 멤버를 의미한다. 예제는 Customer 클래스와 Order 클래스를 명시하고 있다. 코드 생성기는 이 문서를 읽어들여 Customer클래스와 Order클래스를 생성해낼 것이다.
다음으로 생성기를 구현해 보자. 첫번째 할 일은 XML데이터 구조를 표현하기 위한 내부 구조를 만드는 것이다. 그러기 위해 두개의 자바 클래스를 만들 것인데 위의 와 요소를 표현하기 위한 것이다. 를 표현하기 위한 디스크립터 클래스는 다음과 같다.:
// ClassDescriptor.java
package com.codegenerator.example1;
import java.util.*;
public class ClassDescriptor {
private String name;
private ArrayList attributes = new ArrayList();
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void addAttribute(AttributeDescriptor attribute) {
attributes.add(attribute);
}
public ArrayList getAttributes() {
return attributes;
}
}
|
ClassDescriptor클래스는 요소에 명시된 데이터를 담기 위해 사용된다. Name 애트리뷰트는 클래스 이름을 담기 위해 사용하고 attributes는 여러 개의 AttributeDescriptor를 담기 위하여 사용되는데 AttributeDescriptor클래스는 ClassDescriptor와 마찬가지로 요소의 내용을 담기 위해 사용되는 클래스이다. 다음을 보라:
// AttributeDescriptor.java
package com.codegenerator.example1;
import java.util.*;
public class AttributeDescriptor {
private String name;
private String type;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return this.type;
}
}
|
XML파일을 디스크립터 클래스들에 담기 위해 SAX파서를 사용하는데 다음과 같이 한다.:
package com.codegenerator.example1;
import java.util.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class ClassDescriptorImporter extends DefaultHandler {
private ArrayList classes = new ArrayList();
public ArrayList getClasses() {
return classes;
}
public void startElement(String uri, String name,
String qName, Attributes attr) throws SAXException {
// Imports <Class>
if (name.equals("Class")) {
ClassDescriptor cl = new ClassDescriptor();
cl.setName(attr.getValue("name"));
classes.add(cl);
}
// Imports <Attribute>
else if (name.equals("Attribute")) {
AttributeDescriptor at = new AttributeDescriptor();
at.setName(attr.getValue("name"));
at.setType(attr.getValue("type"));
ClassDescriptor parent =
(ClassDescriptor) classes.get(classes.size()-1);
parent.addAttribute(at);
}
else if (name.equals("Content")) {
}
else throw new SAXException("Element " + name + " not valid");
}
}
|
ClassDescriptorImport 클래스가 하는 일은 SAX 기본 핸들러를 익스텐즈하여 XML로부터 디스크립터 클래스로 데이터를 변환하는 것이다. 위에서 볼 수 있듯이, 요소가 한번씩 처리될 때마다 ClassDescriptor 클래스의 새로운 인스턴스를 만들어내고 핸들러의 ArrayList에 삽입한다. 또한 파서는 요소를 처리할 때마다 새로운 ClassAttribute클래스를 생성해내고 부모 ClassDescriptor 인스턴스에 그것을 삽입한다. 파싱 작업의 마지막에 classes변수는 XML문서에 명시된 모든 클래스들의 디스크립터 클래스를 가지게 된다.getClasses 메소드는 ArrayList를 반환한다.
이 시점에서 벨로시티가 등장한다. 자바 클래스와 get/set메소드를 만들어내기 위해 작성된 VTL템플릿은 다음과 같다.:
## class.vm
import java.util.*;
public class $class.Name {
#foreach($att in $class.Attributes)
// $att.Name
private $att.Type $att.Name;
public $att.Type get$utility.firstToUpperCase($att.Name)() {
return this.$att.Name;
}
public void set$utility.firstToUpperCase($att.Name)($att.Type $att.Name) {
this.$att.Name = $att.Name;
}
#end
}
|
$class 라는 레이블은 ClassDescriptor 인스턴스를 가리킨다. 따라서 $class.Name은 결국 ClassDescriptor.getName 를 호출함으로 얻어내는 결과를 출력하는 것이다. ( 실제로 $class.Name 은 $class.getName()의 축약형인데 모든 get 으로 시작하는 메소드에 대하여 이런 축약형이 적용된다.). 해당 ClassDescriptor 에 속한 AttributeDescriptor들의 리스트를 참조하기 위해서는 $class.Attributes 라는 레이블을 사용하면 된다. #foreach 구문은 List인 $class.Attributes에 포함된 모든 항목에 대해 루프를 실행한다. 루프 안쪽의 구문은 $att.Name과 $att.Type에 따른 데이터 멤버와 get/set 메소를 정의하고 있다.
$utility.firstToUpperCase 레이블은 사용자가 정의한 유틸리티 클래스를 호출하게 하는데 인자로 받은 문자열의 첫 글자를 대문자로 바꿔주는 기능을 가지고 있따. 이 메소드는 매우 유용한데 예를 들면 number라는 데이터 멤버로부터 getNumber라는 메소드명을 얻어 낼 때 사용하면 된다.
코드 생성기
이제 남은건 메인 어플리케이션이다. 그것은 XML파일을 읽어서 디스크립터 클래스에 값을 집어넣고 벨로시티를 호출해 템플릿을 통한 변환 작업을 수행하게 된다.
이 기사에서 ClassGenerator라는 전체 어플리케이션의 소스를 얻어낼 수가 있다. 가장 중요한 메소드는 start() 메소드다. 여기에 구현체가 있으니 한번 보라.:
public static void start(String modelFile, String templateFile)
throws Exception {
// Imports XML
FileInputStream input = new FileInputStream(modelFile);
xmlReader.parse(new InputSource(input));
input.close();
classes = cdImporter.getClasses(); // ClassDescriptor Array
// Generates Java classes source code
// by using Apache Velocity
GeneratorUtility utility = new GeneratorUtility();
for (int i = 0; i < classes.size(); i++) {
VelocityContext context = new VelocityContext();
ClassDescriptor cl = (ClassDescriptor) classes.get(i);
context.put("class", cl);
context.put("utility", utility);
Template template = Velocity.getTemplate(templateFile);
BufferedWriter writer =
new BufferedWriter(new FileWriter(cl.getName()+".java"));
template.merge(context, writer);
writer.flush();
writer.close();
System.out.println("Class " + cl.getName() + " generated!");
}
}
|
이 메소드는 입력으로 XML와 템플릿 파일명을 받는다. 메소드 내에서는 이전에 설명된 ClassDescriptorImporter 클래스의 cdImporter 인스턴스와 관계지어진 xmlReader 를 사용하여 데이터를 읽어들이게 된다. 당신이 볼 수 있듯이 getClasses메소드를 통하여 클래스로 뽑아내어질 클래스들의 디스크립터 클래스들을 얻어낼 수가 있다. 루핑 코드 안에서 생성되어지는 context객체는 특히 중요한데 왜냐하면 그것은 디스크립터 클래스 인스턴스와 템플릿간의 연결을 제공하기 때문이다. 사실 Context.put 메소드는 자바 객체를 템플릿 레이블과 매핑을 시키게 된다. 다음과 같은 구문을 실행하는 것이 그런 매핑을 수행한다.:
context.put("class", cl);
context.put("utility", utility);
|
클래스 디스크립터의 인스턴스인 cl 객체는 템플릿에서 $class 레이블로 접근할 수가 있고 유틸리티 클래스는 $utility 레이블을 이용해서 접근할 수가 있다. 마지막의 유틸리티 클래스는 GeneratorUtility 클래스의 인스턴스로 filrstInUpperCase() 메소드를 사용하기 위하여 컨텍스트에 삽입한다.
컨텍스트에 템플릿에서 처리할 데이터를 put한 다음에는 start() 메소드에서 제공받은 대로 특정 템플릿 파일에 대한 Template 객체를 생성하고 merge 메소드를 호출한다. 그러면 템플릿 기반의 변환 작업이 수행된다. 컨텍스트의 데이터는 템플릿에 의해 처리되고 결과는 결과 witer 인스턴스의 스트림에 쓰여지게 된다.
아래 예제에서 코드생성기를 데이터 모델로 ordedr.xml , 템플릿으로 class.vm을 입력으로 사용하여 돌려보면 Customer.java 파일을 얻게 될 것이다. 그 실제 구현 코드를 한번 보자:
import java.util.*;
public class Customer {
// code
private int code;
public int getCode() {
return this.code;
}
public void setCode(int code) {
this.code = code;
}
// description
private String description;
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
}
|
import java.util.*;
public class Order {
// number
private int number;
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
// date
private Date date;
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
this.date = date;
}
// customer
private Customer customer;
public Customer getCustomer() {
return this.customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
|
결론
템플릿 기반의 코드 생성기를 개발하면 두 가지 경우에 그것을 사용할 수가 있다.:
1. 다른 클래스의 생성을 위해 데이터 모델을 변경할 때.
2. 다른 프로그래밍 언어에 맞는 템플릿을 제공하여 다른 언어의 코드를 뽑아내고 싶을 때.
두가지 사용예는 코드 생성기 자체를 건드리지 않고 수행할 수 있다. 따라서 템플릿 기반의 코드 생성기는 특정 언어의 코드만 뽑아내게 설계된 생성기에 비해 훨씬 더 유연하다.
이 기사의 2장에서는 훨씬 더 복잡한 상황에서의 템플릿 코드 생성을 알아 볼 것이다. 특별히 필자는 Internal Model Object(아래 참고자료 7번)을 사용한 템플릿의 사용법에 대해 알아볼 것이며 특정 언어의 코드 생성에 종속적인 벨로시티 컨텍스트를 비종속적인 컨텍스트로 작성하는 방법에 대한 패턴을 알아볼 것이다.