HttpLogService를 이용한 Bundle 구현
WSDD (WebSphere Studio Device Developer)를 이용하여 HttpLog라는 번들을 구현해보기로 한다. HttpLog번들은 특정한 HttpServlet에 의해 엑세스된 트랜젝션의 로그 메시지를 추적하고 기록하는 기능을 갖는다.
/// Step 1. Bundle skeleton 생성
WSDD에서 자바 프로젝트를 통해서 HttpLog라는 번들 스켈레톤을 만든다. 또한 Properties를 통해 OSGi framework library osgi.jar와 servlet.jar의 build path를 수정한다.
/// Step 2. OSGi BundleActivator interface 구현
이제 HttpLogBundleActivator 라는 자바 클래스를 작성하면서 OSGi BundleActivator interface를 구현해보기로 한다. BundleActivator interface는 두개의 메소드를 정의하는데 start()과 stop()이 바로 그것이다. start() method는 번들이 active될 때 OSGi System Framework로부터 invoke된다. 아래의 코드에서 BundleContext argument는 delegation object로 Framework로부터 invoke될때에 구동되는 Operation set이다.
//HttpLogBundleActivator.java public class HttpLogBundleActivator implements BundleActivator { … BundleContext bc; public void start(BundleContext context) throws Exception { bc=context; //to add initialization code here } public void stop(BundleContext context) throws Exception { } … } |
/// Step 3. Register custom service
이제 HttpLogService라는 로그기록을 관리하는 custom service를 구현하기로 한다. 총 3개의 자바 파일을 코딩하는데, 명칭은 각각 다음과 같다. HttpLogService.java, HttpLogServiceImpl.java, HttpLogServiceFacotry.java. 우리는 custom service를 위한 하나의 interface class와 implementation class를 구현하는데 예를 들어 HttpLogService interface의 경우 log() 라는 하나의 메소드를 정의한다.
//HttpLogService.java package httplog; public interface HttpLogService { void log(String message); } //HttpLogServiceImpl.java package httplog; public class HttpLogServiceImpl implements HttpLogService{ static public List messageArray = new ArrayList(); public void log(String message) { HttpLogServiceImpl.messageArray.add(message); } } |
일반적으로 어떤 custom services을 요청하는 번들이(hosting bundle) active될때에, 해당 custom services는 System Framework에 등록이 된다. 따라서 Step2에서 구현했던 HttpLogBundleActivator 클래스에 아래의 start() method를 추가한다.
//HttpLogBundleActivator.java public class HttpLogBundleActivator implements BundleActivator { static final String httpLogServiceName = HttpLogService.class.getName(); … public void start(BundleContext context) throws Exception { registerHttpLogService(); } private void registerHttpLogService(){ HttpLogServiceFactory sf = new HttpLogServiceFactory(); ServiceRegistration sr=bc.registerService(httpLogServiceName,sf,null); } } |
또한 registerService()가 호출될 때, 해당 번들은 정의된 custom service name과 OSGi ServiceFactory interface의 서비스 오브젝트 그리고 해당 서비스의 properties가 포함된 Dictionary object를 제공한다. 각각의 ServiceFactory subclass는 getService()와 ungetService()라는 두개의 메소드를 갖게된다. 우리가 작성하는 코드에서는 sf가 하나의 HttpLogServiceFactory 타입이면서 또한 HttpLogServiceImpl instances를 generate하는 역할을 한다.
//HttpLogServiceFactory.java package httplog; import org.osgi.framework.Bundle; import org.osgi.framework.ServiceFactory; import org.osgi.framework.ServiceRegistration; public class HttpLogServiceFactory implements ServiceFactory{ … public Object getService(Bundle bun, ServiceRegistration sr) { return new HttpLogServiceImpl(); } public void ungetService(Bundle bun, ServiceRegistration sr, Object obj) { } } |
/// Step 4. Consume external service
우리가 작성하는 HttpLog Bundle에 의해서 제공되는 외부서비스는 바로HttpService이다. 사용자는 localhost의 HttpServlet이 억세스한 기록들을 HttpLog bundle이 작성한 레코드를 통해서 추적할 수가 있다. 이러한 기능을 제공하기위해서 HttpLog bundle은 HttpService가 제공하는 인터페이스를 통해서 HttpService bundle을 servlet object로 등록해야만 한다. HttpLog bundle이 start될 때 위의 과정이 자동적으로 일어나게된다.
public class HttpLogBundleActivator implements BundleActivator { … static final String servletURI ="/httplog"; private void initialize(){ //Import HttpService attachToHttp(); //register HttpLogService registerHttpLogService(); } private void attachToHttp(){ httpContext = new HttpContext(){ public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) { return true; } public URL getResource(final String name) { return (URL) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { String resource = name; if (resource.charAt(0) != '/') resource = "/".concat(resource); //$NON-NLS-1$ return getClass().getResource(resource); } }); } public String getMimeType(String name) { return null; } }; ServiceReference httpSR = bc.getServiceReference(HttpService.class.getName()); HttpService http = (HttpService)bc.getService(httpSR); try { http.registerServlet(servletURI, new HttpLogServlet(), null, httpContext); } catch (ServletException e) { e.printStackTrace(); } catch (NamespaceException e) { e.printStackTrace(); } } … } //HttpLogServlet.java package httplog; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; public class HttpLogServlet extends HttpServlet{ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html; charset=UTF-8"); PrintWriter out = res.getWriter(); out.print("<html><head><title>" + "Http Log Display" + "</title></head>"); out.print("<body text=\"#000000\" bgcolor=\"#C0C0C0\" link=\"#0000EE\" vlink=\"#551A8B\" alink=\"#FF0000\">"); List msgArray = HttpLogServiceImpl.messageArray; int len=msgArray.size(); for (int i=0;i<len;i++){ String msg= (String)msgArray.get(i); out.print(msg); } out.println("</body></html>"); } } |
위의 코드를 살펴보면 번들이 초기화될 때 특정한 HttpServlet object이 HttpService에 등록되게 된다. 일반적으로 external service가 구동되기전에 요청 번들은 OSGi System Framework로부터 external service instance가 어디에 위치해있는지 필요하게 된다. 이러한 일련의 과정은 BundleContext에서 getServiceReference()과 getService() 메소드를 제공하므로 수행된다. 두개의 메소드는 필요로 하는 정보들을 service registry에서 찾아서 리턴하는 역할을 한다.
/// Step 5. Migrate to bundle format
코드를 모두 작성하고나면 실제 번들을 패키지 형태로 작성하는 과정을 거치게된다. WSDD에서File->New->SMF를 하고 Bundle Folder를 선택하여 새로운 번들을 생성한다. target bundle folder에서HttpLog 를 선택한다. 나머지 옵션은 선택하지않는다. 다음 단계로 MANIFEST를 작성할 단계이다. MANIFEST.MF 파일은 번들들의 독립성과 정보 공유 및 협업을 위해서 매우 중요한 정보 파일이다. 반드시 일정한 OSGi specification에 의해서 작성되어야 한다. WSDD의 MANIFEST editor는 그림처럼 사용자가 쉽게 작성할수있도록 도움을 준다.
다음의 섹션을 필수적으로 정의해야하는 항목들이다. 다음은 우리가 작성한 예제파일의 경우이다.
- Bundle-Name : HttpLog.
- Bundle-Version : 1.0.0.
- Bundle-Activator : httplog.HttpLogBundleActivator
- Import Services : org.osgi.service.HttpService
- Import Packages : javax.servle, javax.servlet.http, org.osgi.framework, org.osgi.service.http.
OSGi은 하나의 JVM 환경에서 작동한다. 그러나 하나의 VM이라고 하나의 프로세스 또는 싱글 태스크라고 생각하면 잘못된것이다. 앞서도 이야기했지만, OSGi는 비록 하나의 VM위에서 구동하더라도 번들간의 독립성과 협업을 하기위해서 각 번들마다 서로 다른 클래스 로더가 구동하여 작동하게 되어있다. 따라서 각 번들은 MANIFEST file에서 자신의 클래스 패스와 버전, 기본적인 정보들을 레퍼런스 하게된다.
- Export Packages : httplog.
- Export Services : httplog.HttpLogService.
HttpLog project를 Export하면 된다.
/// Step 6. OSGi Server로 Bundle 배포
우리가 작성한 번들을 배포하려면 OSGi System Framework가 탑재된 시스템을 찾고 그곳에 추가하면 된다. WSDD에서는 이런 모든 과정을 매우 쉽게 접근할수있게 도와주는데, 다음의 방법을 사용한다. 우선 [Run] Window를 실행시켜서 OSGi 서버(SMF Server)를 생성하고 구동한다. 이렇게 SMF Server를 구동한후에, Project Explorer에서 [HttpLog project] 클릭하고 SMF->Submit Bundle을 선택하면 번들의 배포는 끝난다. SMF server 구동시에 서버가 되는 타겟에 대한 대화상자가 나오는데, 그곳에 아이디와 패스워드, 아이피등에 대한 기본적인 억세스 정보들을 적는다. 우리는 로컬PC에서 작업을 하기에 Admin@localhost:8080/smf로 기입한다. 이렇게 모든 서버의 구동과 번들의 배포가 마친후에 [SMF perspective]-[SMF Bundle Servers view]에서 HttpLog 번들이 SMF server에 배포된 것을 확인할 수가 있다. IBM 솔루션에서는 이러한 모든 과정들이 WSDD의 Dialog Box에서 자동적으로 이루어지지만, 다른 여타의 OSGi 솔루션에서는 각기 다른 방법으로 로컬 서버 및 원격 서버로(실제 OSGi가 설치된 PC 및 타겟 디바이스) 배포할 수가 있다.
/// Step 7. Bundle testing
이제 우리가 구현한 번들을 배포했고, 테스트하는 단계만 남았다. 이 역시도 테스트 번들을 구현하기로 한다. 테스트 번들을 HttpLogTester로 명명하고, 이 번들은 테스트용으로 HttpLogService를 invoke하는 것이 주된 기능이다. 이 번들이 HttpLogService을 invoke하면 그 다음 단계로 Http log가 화면에 정상적으로 나타난다면 우리가 만든 번들은 성공적으로 작성한것으로 본다. 이 번들 역시 우리가 거쳐왔던 step 1,2를 반복하여 bundle skeleton을 생성한다. 빌드 패스에 httplog.jar을 포함하며 아래의 코드와 같이 HttpLogTesterBundleActivator에 start() method를 코딩한다.
//HttpLogTesterBundleActivator.java class HttpLogTesterBundleActivator implements BundleContext{ … public void start(BundleContext context) throws Exception { bc=context; ServiceReference httpLogSR=bc.getServiceReference(httpLogServiceName); HttpLogService httpLog =(HttpLogService)bc.getService(httpLogSR); httpLog.log("Hello world!"); } … } |
step 2-6를 반복하며 마지막으로 역시 HttpLogTester 번들을 SMF server에 배포한다.
/// Step 8. Test HttpLog in SMF runtime
이제 우리는 배포된 HttpLogTester 번들을 실제 install – activate 시키는 과정을 거쳐서 우리가 만든 번들이 제대로 작동되는지 확인해야한다. SMF Runtime을 실행시키고 HttpLogTester를 install 한다. 이 모든 과정 역시 WSDD의 SMF Bundle Server view에서 지원된다. HttpLogTester 번들이 SMF Bundle Server에 install 되는 순간 필요한 번들을 요청하게되어 HttpLog와 HttpService는 자동적으로 함께 install 되고 ready하게 된다. 이제 로그 메시지를 확인하기위해서 웹브라우저에 http://localhost/httplog라고 타입하면 곧 우리는 브라우저에서 해당 로그 결과를 살펴볼수가 있게 된다.
이번호에서 우리는 OSGi bundle의 기본적인 구조와 코드를 통해서 구현과 배포, 실행에 대해서 살펴보았다. 예제 코드에서 로컬 서버와 번들간의 인터페이스들을 살펴보았지만, 실제 구현되는 코드에서는 DB와의 연동과 원격관리서버를 통한 업데이트와 관리 그리고 마지막으로 네이티브 코드(Native Code)와의 연동을 통해서 OSGi의 장점을 최적화하여 사용하고 있다. 특히 방금 이야기한 DB handling & Sync, Bundle Remote Management & Dynamic Update, JNI를 통한 Native code interface 등은 OSGi의 고급 핵심 기술들로 향후 더욱 많이 사용될 중요한 부분이다. 여타의 OSGi 솔루션들도 마차가지겠지만, 특히 IBM의 OSGi 솔루션은 Eclipse Plugin Framework – Equinox, Business Process Application – Expeditor등을 거쳐서 더욱 강력한 Middleware Framework로 부각되고 있다. 다음호에서는 RCP(Rich Client Platform)로 대변되는 Eclipse OSGi Framework “Equinox”를 통해서 임베디드에서 데스크탑 애플리케이션, 그리고 플랫폼의 일부분으로까지 확장되어가는 OSGi 사례를 살펴본다.
/// 참고문헌
1. Managed mobile clients with OSGi - http://www.ibm.com/developerworks/library/wi-osgi
2. IBM Workplace Client Technology, Micro Edition and Open Services Gateway Initiative (OSGi) –http://www.ibm.com/developerworks/websphere/library/techarticles/0606_salkosuo
/0606_salkosuo.html
3. IBM Service Management Framework - http://www-306.ibm.com/software/wireless/smf/index.html