Writing /volume1/Web/Public/dokuwiki/data/log/deprecated/2024-11-15.log failed

Text internaliztion from a ResourceBundle

코드에 포함되어 있는 다국어를 일반 자바 리소스 번들을 통해 국제화시키는 방법을 소개한다.
지역화된 표시 문자열을 리소스 번들로부터 얻어오는 Utility 메소드를 작성하였다.

import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Locale;
 
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;
 
protected static ClassLoader getCurrentClassLoader(Object defaultObject)
{
    ClassLoader loader =
          Thread.currentThread().getContextClassLoader();
    if (loader == null)
    {
      loader = defaultObject.getClass().getClassLoader();
    }
    return loader;
}
 
public static String getDisplayString(String bundleName, String id,
                                        Object params[],
                                        Locale locale)
{
    String text = null;
    ResourceBundle bundle =
        ResourceBundle.getBundle(bundleName, locale,
                                 getCurrentClassLoader(params));
    try
    {
      text = bundle.getString(id);
    }
    catch (MissingResourceException e)
    {
      text = "!! key " + id + " not found !!";
    }
    if (params != null)
    {
      MessageFormat mf = new MessageFormat(text, locale);
      text = mf.format(params, new StringBuffer(), null).toString();
    }
    return text;
}
...

이 코드는 getCurrentClassLoader라는 메소드로 시작하는데, 이 메소드는 현재 스레드의 클래스 로더나 또는 지정된 기본 객체의 클래스 로더를 리턴한다.
클래스 로더가 왜 필요할까? 리소스 번들을 로드할 때 ResourceBundle 클래스는 리소스 번들에 해당하는 파일을 클래스 경로에서 찾는다.
Utils와 같은 유틸리티 클래스는 대개 다른 클래스 로더에 의해 Web Application의 주된 클래스 경로로부터 로드된다.
만약 이 사실을 고려하지 않는다면 Utils 클래스가 WEB-INF 디렉토리에 존재하지 않는 한 getDisplayString이 올바르게 작동하는 것을 보장하지 못한다.

getDisplayString 메소드는 getCurrentClassLoader로부터 얻어온 ClassLoader 인스턴스를 정적 메소드인 ResourceBundle.getBundle에 전달하여 번들을 로드한다.
그 다음 ResourceBundle.getString 메소드에 식별자를 전달하여 호출함으로써 번들로부터 문자열을 얻어온다.
만약 저장된 식별자를 찾지 못한다면, 이 메소드는 MissingResourceException을 던진다. 이와 같은 경우에 에러 메세지를 리턴하기 위해서는 이 예외를 잡아야 한다.
파라미터의 배열이 들어있다면 MessageFormat 클래스를 사용하여 파라미터들을 문자열에 삽입한다.

이제 실제 코드에서 이 Utils 메소드를 사용하는 예를 보인다.

Example of using Utils.getDisplayString

이 예제는 Custom 보안을 위한 Filter 클래스의 일부이다.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException{
...
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
HttpSession session = httpRequest.getSession();
 
String requestPath = httpRequest.getPathInfo();
...
String text = Utils.getDisplayString(Constants.BUNDLE_BASENAME,
                                             "PathNotFound",
                                             new Object[] { requestPath },
                                             request.getLocale());
        httpResponse.sendError(HttpServletResponse.SC_NOT_FOUND, text);
...

먼저 주목해야 할 것은 이름을 잘못 코딩하는 것을 막기 위해 리소스 번들의 이름을 Costants 클래스로부터 가져온다는 사실이다.
또한 현재의 Locale을 직접 HttpRequest객체로부터 가져왔는데, 왜냐하면 아직 FacesContext가 존재하지 않기 때문이다.1)
번들 내의 문자열이 하나의 파라미터를 포함하고 있으므로 getDisplayString 메소드에 하나의 파라미터로서 requestPath를 전달한다.2)


이번에는 코드에서 Component의 text를 국제화시키는 예이다.

public HtmlSelectOneMenu getProjectSelectOne()
  {
    if (projectSelectOne == null)
    {
      ...
  //    projectSelectOne.setTitle("Select the project type");
      projectSelectOne.setTitle(
        Utils.getDisplayString(getApplication().getMessageBundle(),
                               "ProjectTypeTitle",
                               null,
                               getFacesContext().getViewRoot().getLocale()));
      ...
    return projectSelectOne;
  }

이번에는 JSF API를 사용할 수 있으므로, Application과 ViewRoot로부터 각각 리소스번들의 이름과 Locale을 얻어왔다.
Application.messageBundle 특성은 faces-config.xml에 설정돼 있는 번들을 리턴한다.
faces-config.xml은 커스터마이징된 메세지를 위한 용도도 있지만 표시 문자열을 지역화 하기 위해 사용될 수도 있다.
faces-config.xml에는 오직 하나의 번들만을 지정할 수 있다. 따라서 Application이 다수의 번들을 가지고 있다면, 앞의 예제와 같이 문자열 상수를 사용하여 번들을 로드해야 한다.
이 문자열은 파라미터를 갖지 않으므로 getDisplayString 메소드의 파라미터 배열에 null을 전달했다.

Internalization of Messages

이번에는 코드내에서 정보를 알리기 위한 목적과 에러를 보고하기 위한 목적등으로 쓰이는 메세지를 생성하는 Utils 메소드를 소개한다.
이 메소드는 ResourceBundle로부터 새로운 FacesMessage 인스턴스를 생성시키는 Factory method이다. 3)

...
public static FacesMessage getMessage(String messageId, Object params[],
                                        FacesMessage.Severity severity)
  {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    String bundleName = facesContext.getApplication().getMessageBundle();
    if (bundleName != null)
    {
      String summary = null;
      String detail = null;
      Locale locale = facesContext.getViewRoot().getLocale();
      ResourceBundle bundle =
          ResourceBundle.getBundle(bundleName, locale,
                                   getCurrentClassLoader(params));
      try
      {
        summary = bundle.getString(messageId);
        detail = bundle.getString(messageId + ".detail");
      }
      catch (MissingResourceException e)
      {}
      if (summary != null)
      {
        MessageFormat mf = null;
        if (params != null)
        {
          mf = new MessageFormat(summary, locale);
          summary = mf.format(params, new StringBuffer(), null).toString();
        }
        if (detail != null && params != null)
        {
          mf.applyPattern(detail);
          detail = mf.format(params, new StringBuffer(), null).toString();
        }
        return (new FacesMessage(severity, summary, detail));
      }
    }
    return new FacesMessage(severity, "!! key " + messageId + " not found !!",
                            null);
  }
}
...
public static void reportError(FacesContext facesContext, String messageId,
                                   Exception exception)
  {
    FacesMessage message = getMessage(messageId, null,
                             FacesMessage.SEVERITY_ERROR);
    facesContext.addMessage(null, message);
    if (exception != null)
    {
      facesContext.getExternalContext().log(message.getSummary(), exception);
    }
  }
...

새로운 getMessage 메소드는 getDisplayString과 비슷하다. 다만 요약문과 상세문에 따른 새로운 FaceMessage 인스턴스를 생성시킨다는 점이 다르다.
두 식별자의 유일한 차이는 상세문의 경우 “_detail”로 끝난다는 점이다. 이는 JSF가 내부적으로 표준 메세지와 커스텀 메세지를 다루는 방식이다.
reportError는 식별자에 근거하여 메세지를 로드하는 getMessage를 사용한다.

새로운 팩토리 메소드를 정의했으므로, 코드에서 하드코딩된 메세지를 이 메소드를 이용하여 변경하는 방법을 예를 들어 알아보자.

Example of using Utils.getMessage and Utils.reportError

public String login()
  {
    FacesContext facesContext = getFacesContext();
    Utils.log(facesContext, "Executing AuthenticationBean.getUser()");
 
    User newUser = null;
    try
    {
      newUser = getUserCoordinator().getUser(loginName, password);
    }
    catch (ObjectNotFoundException e)
    {
      facesContext.addMessage(null,
                              Utils.getMessage("BadLogin", null,
                                               FacesMessage.SEVERITY_INFO));
 
  /*      facesContext.addMessage(null,
                              new FacesMessage(FacesMessage.SEVERITY_WARN,
                              "Incorrect name or password.", "")); */
      return Constants.FAILURE_OUTCOME;
    }
    catch (DataStoreException d)
    {
       Utils.reportError(facesContext, "ErrorLoadingUser", d);
 
  /*      Utils.reportError(facesContext, "A database error has occurred.",
                                       "Error loading User object", d);*/
      return Constants.ERROR_OUTCOME;
    }
...

변경된 부분의 getUser를 호출한 뒤 예외를 처리하는 곳이다. ObjectNotFoundException은 중요한 에러가 아니므로, 이 경우 BadLogin이라는 식별자와 함께 getMessage 메소드를 사용하여 INFO 수준의 심각도를 갖는 메세지를 추가했다.
DataStoreException은 심각한 에러이기 때문에, ErrorLoginUser 식별자와 함께 reportError를 호출했다. 4)

1)
필터는 JSF가 요청처리를 시작하기 전에 실행된다.
2)
이로써 “{0}“은 requestPath의 값으로 대체될 것이다.
3)
우리는 몇 개의 파라미터를 받는 하나의 팩토리 메소드를 작성했다. 기본적으로 사용할 수 있는 편리한 메소드를 작성해 놓는것이 좋을 것이기 때문에, 이 메소드에서는 locale이나 severity같은 파라미터를 지정하지 않았다. 만약 이런 메소드를 직접 작성하기를 원하지 않는다면, JSF RI의 CarDemo Application에 포함되어있는 MessageFactory 클래스를 사용해도 좋을 것이다.
4)
코드 내에서 메세지를 생성시키는 것은 많은 양의 작업을 필요로 한다. FacesMessage를 위한 팩토리 메소드는 우리가 직접 작성할 것이 아니라 표준 API의 일부여야 한다고 생각한다. JSF의 차기 버전에서는 이것이 반영되기를 희망한다.

QR Code
QR Code study:jsf:logging (generated for current page)