예제4: 동적 사전 클라이언트 번들

예제 3에서는 간단한 사전 클라이언트 번들을 만들어 보았다. 이 예제에서 문제점은 사전 서비스의 동적인 변화를 감지하지 못한다는 점이다. 그래서 클라이언트가 사용중인 상태에서 사전 서비스를 내리면 에러가 발생하게 된다. 이번 예제에서는 사전 서비스의 상태 변화를 감지하도록 클라이언트를 만들어 보겠다. 결과적으로 좀 튼튼해 지는 것이다.

새 클라이언트의 동작은 기본적으로는 구 클라이언트의 그것과 동일하다. 표준입력으로부터 입력을 단어를 입력받고 사전 서비스를 이용해서 그 단어의 존재를 검사한다. 이 번들은 번들 컨택스트를 이용해서 이벤트 listener로 자신을 등록한다. 이렇게 해서 사전 서비스의 이용가능 여부서비스 이벤트를 감지할 수 있다. 클라이언트는 발견된 서비스 중 첫 번째 서비스를 사용한다. 아래가 구현한 소스코드인 Activator.java이다.

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

package tutorial.example4;

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 org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;

import tutorial.example2.service.DictionaryService;

/**
  * 이 클래스는 사전 서비스를 사용해서 단어의 철자가
  * 올바른지 검사한다. 첨자 검사는 해당 단어가 사전에 존재하는
  * 지 검사하는 것으로 이루어 진다.
  * 이 번들은 사전 서비스의 사용여부를 감지하기 때문에
  * 예제 3의 그것보다 더 복잡하다.
  * 사용하고 있던 사전 서비스가 내려졌을 경우 gracefully
  * 사용을 중지하고 서비스를 필요로 하고 있을 때 서비스가
  * 돌아오면 자동으로 그 서비스를 사용하게 된다.
  * 이 번들은 처음 구한 서비스를 사용한다.
  * 번들이 start되면 start()를 호출한 쓰레드가 그대로 표준입력으로 부터
  * 사용자 입력을 받는 처리를 속행하게 된다.
  * 공백행을 입력하면 처리가 종료되지만, 다시 start하려면
  * 번들을 stop한 다음 다시 start해야 한다.
  **/
public class Activator implements BundleActivator, ServiceListener
{
    // 번들의 컨텍스트
    private BundleContext m_context = null;
    // 사용하고 있는 서비스의 레퍼런스
    private ServiceReference m_ref = null;
    // 사용하고 있는 서비스 객체
    private DictionaryService m_dictionary = null;

    /**
     * BundleActivator.start() 메서드를 구현한다.
     * 자신을 서비스 이벤트의 listerner로 등록하고
     * 활성화된 사전 서비스를 쿼리해서 존재한다면
     * 그 첫 번째 서비스의 레퍼런스를 저장하고
     * 단어 검사 루프를 시작한다. 만약 사용할 수 있는
     * 서비스가 발견되지 않았을 경우에는 바로 단어 검사 루프로
     * 들어가지만 사전 서비스가 도착할 때까지 단어를 검사할
     * 수는 없을 것이다. 사전이 도착하면 다른 곳에서 사용하고 있지
     * 않다면 자동으로 사용되게 된다.
     * 사전을 가지게 되면 표준입력으로부터 단어를 입력받아서
     * 그 사전을 사용해서 단어의 존재 검사를 하게 된다.
     * (NOTE:이 예제에서처럼 호출한 쓰레드를 그대로 처리에
     *  사용하는 것은 좋지 못한 사용법이다. 단지 tutorial용으로만
     *  사용할 뿐이다)
     * @param context 번들의 프레임워크 컨택스트.
    **/
    public void start(BundleContext context) throws Exception
    {
        m_context = context;

        // 사전 서비스에 관계하는 이벤트를 listen하게 한다
        m_context.addServiceListener(this,
            "(&(objectClass=" + DictionaryService.class.getName() + ")" +
            "(Language=*))");

        // 언어에 제한없이 서비스 레퍼런스를 쿼리한다.
        ServiceReference[] refs = m_context.getServiceReferences(
            DictionaryService.class.getName(), "(Language=*)");

        // 사전 서비스를 발견한 경우 그 중 첫번째의
        // 페러런스를 저장해 둔다.
        if (refs != null)
        {
            m_ref = refs[0];
            m_dictionary = (DictionaryService) m_context.getService(m_ref);
        }

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

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

                // 빈 라인을 입력한 경우 종료한다.
                if (word.length() == 0)
                {
                    break;
                }
                // 사전이 없을 경우 메세지 출력.
                else if (m_dictionary == null)
                {
                    System.out.println("No dictionary available.");
                }
                // 있을 경우 단어를 검사.
                else if (m_dictionary.checkWord(word))
                {
                    System.out.println("Correct.");
                }
                else
                {
                    System.out.println("Incorrect.");
                }
            }
        } catch (Exception ex) { }
    }

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

    /**
     * ServiceListener.serviceChanged()를 구현한다. 사용하고 
     * 있던 서비스가 없어졌는 지 검사하고 필요할 때 서비스를
     * get을 시도한다.
     * @param event the 발생한 서비스 이벤트.
    **/
    public void serviceChanged(ServiceEvent event)
    {
        String[] objectClass =
            (String[]) event.getServiceReference().getProperty("objectClass");

        // 사전 서비스가 등록되었을 때, 필요한 지 검사한다.
        // 필요하다면 레퍼런스를 취득한다.
        if (event.getType() == ServiceEvent.REGISTERED)
        {
            if (m_ref == null)
            {
                // 서비스 객체에 대한 레퍼런스를 취득한다.
                m_ref = event.getServiceReference();
                m_dictionary = (DictionaryService) m_context.getService(m_ref);
            }
        }
        // 사전 서비스가 삭제되었을 때, 사용하고 있던 서비스였는지
        // 검사한다. 사용하고 있었다면, 서비스를 반환하고 다른 서비스를
        // 취득하기를 시도한다.
        else if (event.getType() == ServiceEvent.UNREGISTERING)
        {
            if (event.getServiceReference() == m_ref)
            {
                // 서비스 객체를 반환하고, 레퍼런스를 null로 셋팅한다.
                m_context.ungetService(m_ref);
                m_ref = null;
                m_dictionary = null;

                // 다른 서비스를 취득할 수 있는 지 쿼리해 본다.
                ServiceReference[] refs = m_context.getServiceReferences(
                    DictionaryService.class.getName(), "(Language=*)");
                if (refs != null)
                {
                    // 첫 번째 서비스 객체의 레퍼런스를 취득한다.
                    m_ref = refs[0];
                    m_dictionary = (DictionaryService) m_context.getService(m_ref);
                }
            }
        }
    }
}

클라이언트는 사전 서비스의 도착과 떠남을 알리는 이벤트를 listen한다. 새 사전 서비스가 도착하면 현재 사용하고 있는 서비스가 없다면 도착한 서비스를 취득해서 사용하게 된다. 존재 하고 있던 사전 서비스가 삭제되었다면 현재 사용하고 있던 서비스인지 검사해서 만약 그렇다면 서비스를 반환하고 다른 사전 서비스를 알아보게 된다. 아니였다면 그 이벤트는 무시하게 된다.


Comments