예제3: 사전 클라이언트 번들

이번 예제에서는 예제 2에서 만든 사전 서비스를 이용하는 클라이언트 번들을 제작해 보겠다. 아래의 소스는 사전 서비스를 쿼리하는 번들이다. 이 번들은 첫 번째 사전 서비스를 검색한다. 만약 해당 서비스가 존재하지 않다면 메세지를 출력하고 stop한다. OSGi의 서비스의 사용법은 JAVA의 interface의 사용법과 같다. 그저 사전 서비스 인터페이스로 형변환을 하기만 하면 된다. 소스 파일은 Activator.java가 된다.

/*
 * OSGi and Gravity Service Binder tutorial.
 * Copyright (c) 2003  Richard S. Hall
 * http://oscar-osgi.sourceforge.net
**/

package tutorial.example3;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

import tutorial.example2.service.DictionaryService;

/**
 * 이 클래스는 사전 서비스를 사용해서 단어의 철자가
 * 올바른지 검사한다. 첨자 검사는 해당 단어가 사전에 존재하는
 * 지 검사하는 것으로 이루어 진다.
 * 이 번들은 처음 구한 서비스를 사용한다. 동적으로 가용
 * 서비스를 모니터링을 하지는 않는다. (사전 서비스가 활성화
 * 되거나 비활성화 되는 것을 listen하지 않는다)
 * 번들이 start되면 start()를 호출한 쓰레드가 그대로 표준입력으로 부터
 * 사용자 입력을 받는 처리를 속행하게 된다.
 * 공백행을 입력하면 처리가 종료되지만, 다시 start하려면
 * 번들을 stop한 다음 다시 start해야 한다.
**/
public class Activator implements BundleActivator
{
    /**
     * BundleActivator.start() 메서드를 구현한다.
     * 활성화된 사전 서비스를 쿼리해서 존재하지 않을 경우
     * 메세지를 출력하고 리턴한다. 존재한다면 표준입력으로
     * 부터 단어를 읽어서 발견된 사전 서비스 중 첫번 째 서비스를
     * 사용해서 단어가 존재하는지 검색한다.
     * (NOTE:이 예제에서처럼 호출한 쓰레드를 그대로 처리에
     *  사용하는 것은 좋지 못한 사용법이다. 단지 tutorial용으로만
     *  사용할 뿐이다)
     * @param context 번들의 프레임워크 컨택스트.
    **/
    public void start(BundleContext context) throws Exception
    {
        // 모든 언어에 매칭하는 서비스의 레퍼런스를 쿼리
        ServiceReference[] refs = context.getServiceReferences(
            DictionaryService.class.getName(), "(Language=*)");

        if (refs != null)
        {
            try
            {
                System.out.println("Enter a blank line to exit.");
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                String word = "";

                // 무한 루프
                while (true)
                {
                    // 단어를 입력받는다.
                    System.out.print("Enter word: ");
                    word = in.readLine();

                    // 공백라인을 입력한 경우 루프를 빠져 나간다.
                    if (word.length() == 0)
                    {
                        break;
                    }

                    // 먼저 서비스를 취득하뒤 단어의 존재를 검사
                    DictionaryService dictionary =
                        (DictionaryService) context.getService(refs[0]);
                    if (dictionary.checkWord(word))
                    {
                        System.out.println("Correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect.");
                    }

                    // 사전 서비스를 되돌린다.
                    context.ungetService(refs[0]);
                }
            } catch (IOException ex) { }
        }
        else
        {
            System.out.println("Couldn't find any dictionary service...");
        }
    }

    /**
     * BundleActivator.stop() 메서드를 구현한다. 프레임워크가
     * 자동으로 등록된 서비스를 제거해 주기 때문에 구현내용은 비워 둔다.
     * @param context 프레임워크의 번들용 컨택스트.
    **/
    public void stop(BundleContext context)
    {
        // NOTE: 자동으로 서비스가 제거된다.
    }
}

번들의 메타데이터를 저장하는 manifest.mf 파일을 아래와 같이 작성한다. 

Bundle-Activator: tutorial.example3.Activator
Import-Package: tutorial.example2.service
Bundle-Name: Dictionary client
Bundle-Description: A bundle that uses the dictionary service if it finds it at startup
Bundle-Vendor: Richard Hall
Bundle-Version: 1.0.0

Bundle-Activator 속성에 BundleActivator를 구현하는 클래스를 기술하고, 이 번들이 필요로 하는 공유 패키지를 Import-Package 속성에 기술한다. 이 import 속성은 예제 2에서 만든 번들에 의해서 충족되게 된다. 왜냐하면 예제2가 이 번들이 import하는 패키지를 export 해주기 때문이다. import 패키지에 대한 상세한 조작은 OSGi 프레임워크가 자동으로 해주게 된다.

컴파일 하려면 osgi.jar 파일이 클래스패스 내에 존재해야 한다. (Oscar의 lib 디렉토리에 들어 있다) 또한 example2.jar 파일도 클래스패스에 들어 있어야 한다. 컴파일은 아래와 같은 명령으로 실행한다.

javac -d c:\classes *.java

실행하면 모든 소스 파일을 컴파일해서 그 결과를 c:\classes 디렉토리의 서브디렉토리에 생성한다. 서브디렉토리명은 소소코드에 정의한 패키지명이 되므로 여기서는 tutorial\example3가 된다. 위의 명령이 실패하지 않으려면 c:\classes 디렉토리가 존재해야 한다. 컴파일이 끝나면 생성된 패키지 디렉토리를 JAR로 묶어야 한다. 이때 번들의 메타데이터가 들어 있는 manifest 파일도 JAR파일에 같이 묶어야 한다. JAR 파일을 생성하기 위해 아래와 같이 실행한다.

jar cfm example3.jar manifest.mf -C c:\classes tutorial\example3

실행하면 manifest 파일을 사용하는 c:\classes 디렉토리의 tutorial\example3 디렉토리에 있는 자바 클래스 파일들로 JAR 파일을 생성한다. JAR 파일이 생성되었다면 이제 번들을 install하고 start할 준비가 되었다. 

Oscar의 사용법은 usage.html를 참고한다. Oscar를 실행하면 프로파일명을 물어 본다. 우리는 모든 번들을 tutorial이라는 프로파일에 넣을 것이다. Oscar를 실행한 후, 예제 2의 사전 서비스 이외의 모든 번들이 stop되어 있는지 확인해야 한다.. ps 라는 Oscar 쉘 명령을 사용해서 모든 번들의 일람과 상태와 식별번호를 확인할 수 있다. 예제 2의 번들이 활성화 되어 있지 않다면 start 명령에 ps로 확인한 해당 번들의 식별번호를 사용해서 start 시킨다. 그 외의 번들 중에 start되어 있는 서비스는 stop명령으로 stop시킨다. (Oscar는 쉘을 제공하는 번들이 있다. 이 번들은 stop하지 않도록 주의한다.) 이제 사전 클라이언트 번들을 install하고 start할 준비가 되었다. c:\tutorial 이라는 디렉토리에 사전 클라이언트 번들을 생성했다고 하면, Oscar의 쉘에서 아래와 같은 명령을 사용해서 install과 start를 실행할 수 있다.

start file:c:/tutorial/example3.jar

위의 명령은 install과 start를 모두 한번에 실행한다. install과 start를 각각 분리해서 Oscar 쉘에 명령을 내릴 수도 있다. 번들을 start시키면 쉘 쓰레드를 사용해서 단어를 물어 볼 것이다. 단어를 검사하련 단어를 입력하고 종료하려면 그냥 엔터를 입력한다. 번들을 재시작 하려면 Oscar의 ps 쉘 명령어로 사전 서비스 번들의 식별번호를 확인해서 그 번호를 인수로 stop 명령으로 stop시킨 후 start 명령으로 start시킨다. 사전 서비스를 테스트하려면 사전에 등록된 단어("welcome", "to", "the", "OSGi", "tutorial")와 등록되지 않은 단어를 각각 입력해서 결과를 확인한다. 

이 예제 클라이언트 매우 간단하다. 아니, 사실 너무 간단한 예이다. 만약에 사전 서비스가 갑자기 중지되어 버린다면? 클라이언트가 서비스에 접근할 때 null pointer exception이 발생하게 된다. 이와 같이 동적으로 서비스의 가용성이 바뀐다는 점이 OSGi 프레임워크의 중심 개념이다. 결론적으로 우리는 이 클라이언트를 이러한 경우에도 대처할 수 있도록 튼튼하게 만들 필요가 있다. 예제 4에서 서비스의 상태를 동적으로 모니터링하는 좀 더 복잡한 클라이언트를 만들어 볼 것이다. 

원문:http://oscar-osgi.sourceforge.net/tutorial/ex3.html
번역: 허 련호(airless at funit.net)
2009/05




Comments