제공 : 한빛 네트워크
저자 : Sunil Patil
역자 : 이대엽
원문 : http://www.onjava.com/pub/a/onjava/2006/11/08/advanced-java-content-repository.html
JSR-170로서 구체화되어 있는 자바 컨텐츠 저장소 (JCR; Java Content Repository) API는 컨텐츠 저장소 접근에 사용되는 API를 표준화하기 위한 시도이다. 이 기사에서 우리는 JSR API에 정의되어 있는 고급기능 및 선택적인 기능들에 대해 알아볼 것이다. 우리는 여러분이 새로운 노드나 속성을 추가하는 방법이나 아파치 잭래빗의 설정 방법 등과 같은 JCR의 기본적인 기능들에 대해서는 이미 익숙하다고 가정한다. 그러나 만일 여러분이 이러한 주제들에 대해 익숙치 않거나 단순히 여러분의 기억을 상기시키고자 한다면 먼저
"자바 컨텐츠 저장소란 무엇인가" 기사를 확인해 보길 바란다.
이 기사에서는 JSR-170의 참조구현체이자
아파치에서 관장하고 있는 오픈소스 프로젝트인
잭래빗을 사용할 것이나 여러분이 선택한 다른 어떤 JSR-170 규격을 준수하는 컨텐츠 저장소를 사용하는 것도 무방하다.
서로 다른 벤더들의 컨텐츠 관리시스템 (Content Management System)이 한동안 시장에 출시되었다. 이러한 벤더들은 모두 그들의 CMS 시스템에 자신들만의 전용 컨텐츠 저장소를 탑재하고 있으며 일반적으로 그들의 컨텐츠 저장소에만 접근하기 위한 전용 API를 제공하고 있다. JSR-170의 주요 목표 중 하나는 이러한 CMS 벤더들이 JSR-170의 도입을 용이하게 하는 것이었다. 그렇게 하기 위하여 JSR-170의 기능들은 3가지 부분으로 분화되었다. 첫 번째는 레벨 1 기능으로서 저장소에 대한 읽기전용 뷰를 제공한다. 레벨 2는 읽고 쓰기 오퍼레이션에 필요한 기본적인 기능들을 정의한다. 이러한 레벨 2에 추가적으로 JSR-170은 컨텐츠 저장소에 의해 구현될 수 있는 버전관리(versioning), 관찰(observation), 잠금(locking), SQL 검색(SQL search), 트랜잭션(transaction)의 다섯 가지의 선택기능들을 정의한다. 이는 레벨1의 규약을 준수하는 저장소를 가진 벤더는 SQL 검색 기능은 구현하더라도 다른 고급기능들은 구현하지 않아도 되게끔 결정할 수 있다는 것을 의미한다. 따라서 다른 어떤 벤더는 다른 기능집합, 예를 들어 버전관리와 관찰기능을 가진 레벨 2의 규약을 준수하는 저장소를 구현할 수도 있다.
이 기사는 JSR-170에 정의되어 있는 가장 인기 있는 2가지 선택기능을 사용하는 애플리케이션을 개발하는 방법에 관한 단계별 가이드이다. 우리는 먼저 버전관리에 대해 알아보는 것으로 시작할 것이며 다음에 이어서 저장소 내의 특정 저장내용에 변화가 발생할 경우 여러분이 어떤 비즈니스 로직을 실행할 수 있게끔 해주는 관찰(observation)에 관해 알아볼 것이다.
예제 애플리케이션
이 기사에서 우리는 아파치 잭래빗을 이용하여 "자바 컨텐츠 저장소란 무엇인가(What is Java Content Repository)"에 개발했던 예제 블로그 애플리케이션의 API를 수정하여 JSR-170에 정의되어 있는 고급기능들을 시연할 것이다. 잭래빗은 레벨 1과 레벨 2를 준수하는 것뿐만 아니라 JSR-170의 모든 선택적인 기능들도 구현한다는 것을 유념해 주기 바란다.
이 예제 블로그 애플리케이션은 스트러츠 1.2를 자체 MVC 프레임워크로 사용하는 웹 애플리케이션이다. 이 애플리케이션은 여러분이 새로운 블로그 항목의 게시, 기존 블로그 항목의 수정 및 삭제, 그리고 블로그 항목에 이미지를 첨부할 수 있도록 해준다. 예제 블로그 애플리케이션은 두 부분으로 구성되는데, 사용자 인터페이스(UI) 부분에서는 사용자로부터 입력내용을 받은 것을 처리하여 결과를 보여주며, 두 번째 부분에서는 실질적인 컨텐츠 저장소와의 소통을 수행한다. 우리는 BlogEntryDAO 인터페이스를 작성하였는데, 이는 이러한 두 부분 사이에서의 계약 역할을 한다. 시간을 절약하기 위해 UI 부분을 구축하는 방법에 관해서는 언급하지 않는 대신 오직 데이터에만 집중할 것이다.
예제 코드를 다운받아 거기서 직접 UI 코드를 복사하길 바란다.
우리의 예제 애플리케이션에서 가장 먼저 하는 일은 BlogEntryDAO 인터페이스를 다음과 변경하여 버전관리 기능에 필요한 메소드를 정의하는 것이다:
public interface BlogEntryDAO {
//Basic methods implemented in first part
public ArrayList getVersionList(String blogTitle)
throws BlogApplicationException;
public void restoreVersion(String blogTitle,String versionName)
throws BlogApplicationException;
}
첫 번째 메소드인 getVersionList()는 blogList의 제목을 받아 관련 blogEntry에 대한 모든 버전의 목록을 리턴한다. restoreVersion() 메소드는 지정된 버전명을 사용하는 블로그 항목의 버전을 복원하는 데 사용된다.
버전관리
버전관리에 관해 첫 번째로 논의할 내용은 컨텐츠 저장소의 문맥상 용어가 무엇을 의미하느냐이다. JSR-170은 버전관리를 노드의 상태가 차후에 복원 가능한 형태로 기록되도록 저장하는 것으로 정의하고 있다. 버전관리는 선택적인 기능이므로 여러분은 먼저 repository.getDescriptor("OPTION_VERSIONING_SUPPORTED")를 호출하여 여러분의 저장소가 버전관리를 지원하는지의 여부를 확인해야 한다.
버전관리가 이루어지는 저장소에서는 작업공간(workspace)은 버전관리가 가능한 노드와 버전관리가 불가능한 노드 모두를 가질 수 있다. 여러분은 특정 노드에 믹스인(mixin) 타입으로 mix:versionable을 추가함으로써 그 노드가 버전관리가 가능한 것이라 표시할 수 있다. 모든 버전관리가 가능한 노드는 읽기 전용으로 표시되며 따라서 매번 여러분이 그것을 변경하고자 할 경우 먼저 node.checkout()를 호출하여 그 노드로부터 읽기전용 속성을 제거할 필요가 있다. 원하는 대로 변경한 다음 node.save()를 호출하여 변경사항을 저장한다. 그런 다음에 그 노드의 읽기전용 상태를 복구하고 시스템에 의해 생성된 노드를 가진 새 버전을 생성하는 node.checkin()을 호출해야 한다.
만약 여러분의 저장소가 버전관리가 가능한 것이라면 저장소는 하나 혹은 그 이상의 작업공간에 추가적으로 특별한 버전 저장영역을 갖게 될 것이다. JCR API의 가장 멋진 부분은 버전 데이터를 저장하는 데 있어 동일한 구조의 노드와 속성을 사용한다는 것이다. 그것은 모든 버전관리가 가능한 노드를 가지는 nt:versionHistory라는 한 노드와 관련되어 있다. 이 노드는 특정 노드에 대한 버전 히스토리를 담고 있으며 항상 기본적인 버전(생성시점의 노드 상태)을 담고 있는 하나의 루트 노드를 가지게 된다. 그 후에는 여러분이 변경사항을 체크인할 때마다 하나의 새로운 노드가 생성되며 계승 관계로서 이전 버전의 노드와 연관되어진다. 모든 버전 자체는 nt:version 타입의 노드이며 jcr:frozenNode 속성에 버전이 생성되는 시점의 노드 상태를 저장할 것이다.
지금까지 논의의 초점을 대부분 버전관리의 이론적인 측면에만 맞추었다. 이제 우리의 예제 블로그 애플리케이션이 버전관리를 사용하도록 변경하여 논의의 초점을 다음 단계로 옮겨보도록 하자. 우리는 예제 블로그 애플리케이션을 변경하여 모든 블로그 항목에 "버전링크 표시(display version list)" 링크가 나타나게끔 하고자 한다. 이 링크에 의해 나열된 각각의 버전은 blogEntry를 특정 버전으로 복원하기 위한 "복원(restore)" 링크를 갖게 된다. 다음의 단계를 따라 예제 블로그 애플리케이션을 변경해보자.
- 여느때와 같이 여러분은 예제 애플리케이션 코드로부터 이 부분에 대한 UI 부분을 복사할 수 있다.
- JackrabbitBlogEntryDAO 클래스의 insertBlogEntry() 메소드를 다음과 같이 변경한다:
public void insertBlogEntry(BlogEntryDTO blogEntryDTO) throws BlogApplicationException {
Session session = JackrabbitPlugin.getSession();
Node rootNode = session.getRootNode();
Node blogEntry = rootNode.addNode("blogEntry");
blogEntry.addMixin("mix:versionable");
blogEntry.setProperty(PROP_TITLE, blogEntryDTO.getTitle());
blogEntry.setProperty(PROP_BLOGCONTENT, blogEntryDTO.getBlogContent());
blogEntry.setProperty(PROP_CREATIONTIME, blogEntryDTO.getCreationTime());
blogEntry.setProperty(PROP_BLOGAUTHOR, blogEntryDTO.getUserName());
session.save();
}
먼저 mix:versionable라 불리는 믹스인 타입을 blogEntry에 추가하고자 한다. 이렇게 하고 나면 컨텐츠 저장소는 blogEntry를 저장한 후에 그것을 읽기전용 상태로 놓을 것이다. 그러나 이 변경사항은 버전관리되는 것과 버전관리가 되지 않는 항목이 서로 섞인 채로 버전관리가 가능한 새로운 항목만 만들어낼 것이다. 여러분은 기존 컨텐츠 저장소를 삭제하고 새로운 블로그 항목들을 그곳에 추가함으로써 이 문제를 해결할 수 있으나 아파치 잭래빗은 컨텐츠 저장소를 삭제하는 내장된 방법을 제공하지 않는다. 따라서 컨텐츠 저장소를 삭제하는 유일한 방법은 c:/temp/blogging의 내용을 지우는 것뿐이다.
- 다음으로 updateBlogEntry() 메소드를 다음과 같이 변경한다:
public void updateBlogEntry(BlogEntryDTO blogEntryDTO)
throws BlogApplicationException {
Session session = JackrabbitPlugin.getSession();
Node blogEntryNode = getBlogEntryNode(blogEntryDTO.getTitle(),
session);
blogEntryNode.checkout();
blogEntryNode.setProperty(PROP_BLOGAUTHOR, blogEntryDTO.getUserName());
blogEntryNode.setProperty(PROP_BLOGCONTENT, blogEntryDTO
.getBlogContent());
blogEntryNode.setProperty(PROP_CREATIONTIME, blogEntryDTO
.getCreationTime());
session.save();
blogEntryNode.checkin();
}
이전 논의에서 언급했던 것처럼 여러분이 한 노드를 버전관리가 가능한 것이라 표시하게 되면 저장소는 그것을 읽기전용 상태로 놓을 것이며 따라서 그 결과 여러분은 그 노드에 대해 아무것도 변경할 수 없게 될 것이다. 따라서 블로그 항목을 업데이트 할 경우 여러분이 가장 먼저 해야 할 일은 블로그 항목을 기록가능하게 만들어줄 checkout()를 호출하는 것이다. 그리고 나서 여러분은 setProperty() 메소드를 호출함으로써 노드의 상태를 변경할 수 있으며, 그런 다음 session.save()를 호출하여 변경사항을 저장한다. 마지막으로 blogEntry.checkin()를 호출한다.
- JackrabbitBlogEntryDAO내의 다른 메소드들도 비슷한 방식으로 변경한다.
- 이제 파라미터로 블로그 항목의 제목을 받아와 관련된 특정 블로그 항목의 버전의 목록을 리턴하는 getVersionList() 메소드를 구현한다.
public ArrayList getVersionList(String blogTitle) throws BlogApplicationException {
ArrayList versionList = new ArrayList();
Node blogEntryNode = getBlogEntryNode(blogTitle);
VersionHistory blogEntryVersionHistory = blogEntryNode.getVersionHistory();
VersionIterator blogEntryVersionIt = blogEntryVersionHistory.getAllVersions();
blogEntryVersionIt.skip(1);
while(blogEntryVersionIt.hasNext()){
Version version = blogEntryVersionIt.nextVersion();
NodeIterator nodeIterator = version.getNodes();
while(nodeIterator.hasNext()){
Node node = nodeIterator.nextNode();
String title = node.getProperty(PROP_TITLE).getString();
String blogContent = node.getProperty(PROP_BLOGCONTENT).getString();
String blogAuthor = node.getProperty(PROP_BLOGAUTHOR).getString();
String versionName = version.getName();
Calendar creationTime = node.getProperty(PROP_CREATIONTIME).getDate();
VersionEntryDTO blogEntryDTO = new
VersionEntryDTO (blogAuthor, title, blogContent,
creationTime, versionName);
versionList.add(blogEntryDTO);
}
}
return versionList;
}
첫 번째 단계는 getVersionHistory()을 호출하여 블로그 항목에 첨부되어 있는 nt:versionHistory 노드에 접근하는 것이다. VersionHistory 객체는 nt:versionHistory 노드를 래핑하고 있다. 다음으로 VersionHistory 객체에 대해 getAllVersions()를 호출하면 반복자가 리턴될 것이다. 버전 히스토리는 항상 적어도 하나의 버전, 즉 루트 버전을 가지고 있다. 여러분은 버전 객체에 getNodes() 메소드를 호출하여 그 버전의 Version 객체(nt:version 노드를 래핑하는)인 자식 노드에 접근할 수 있다. 각각의 자식 노드는 버전이 생성되는 시점에서의 blogEntry의 상태를 담고 있으며 그 상태로부터 우리는 BlogEntryDTO를 생성할 수 있다.
- 마지막 단계는 blogEntry를 유일하게 식별해주는 blogTitle과 versionName의 두개의 파라미터를 받아들이는 restoreVersion()를 구현하는 것이다.
public void restoreVersion(String blogTitle, String versionName)
throws BlogApplicationException {
Node blogEntryNode = getBlogEntryNode(blogTitle);
blogEntryNode.restore(versionName,true);
}
blogEntry 노드를 갖게 되면 여러분은 복원시킬 것의 versionName과 함께 restore()를 호출할 수 있게 된다. 이 메소드의 두 번째 파라미터는 여러분이 복원하고자 하는 노드의 UUID가 이 노드의 하위 트리 외부에 이미 존재하고 있을 경우 어떻게 할지를 나타내는 플래그이다. 만약 그렇게 될 경우 플래그가 true이면 새로 생기는 노드가 우선권을 갖게 된다.
이제 여러분의 소스코드를 빌드하고 이 애플리케이션을 서버에 배포한다. 먼저 새로운 blogEntry 하나를 추가하고 그 노드를 두 세 번 편집하여 변경한다. 이제 "버전 히스토리 표시(Display Version History)" 링크를 클릭한다. 링크를 클릭하면 그 블로그 항목에 대한 버전 목록이 담긴 페이지로 이동하게 될 것이며 각각의 나열된 버전은 복원 링크를 가지고 있을 것이다. 여러분이 특정 버전에 대한 링크를 클릭하게 되면 blogEntry는 특정 버전에 저장되어 있는 상태로 복원될 것이다.