====== 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 메소드를 사용하는 예를 보인다.
{{keywords> FacesMessage i18n Internalization Locale ClassLoader getMessage reportError}}
===== 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%%**가 존재하지 않기 때문이다.((필터는 JSF가 요청처리를 시작하기 전에 실행된다.))\\
번들 내의 문자열이 하나의 파라미터를 포함하고 있으므로 **getDisplayString** 메소드에 하나의 파라미터로서 requestPath를
전달한다.((이로써 "{0}"은 requestPath의 값으로 대체될 것이다.))
----
이번에는 코드에서 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이다.
((우리는 몇 개의 파라미터를 받는 하나의 팩토리 메소드를 작성했다. 기본적으로 사용할 수 있는 편리한 메소드를 작성해 놓는것이
좋을 것이기 때문에, 이 메소드에서는 locale이나 severity같은 파라미터를 지정하지 않았다. 만약 이런 메소드를 직접 작성하기를
원하지 않는다면, JSF RI의 %%CarDemo%% Application에 포함되어있는 **%%MessageFactory%%** 클래스를 사용해도 좋을 것이다.))
...
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**를 호출했다.
((코드 내에서 메세지를 생성시키는 것은 많은 양의 작업을 필요로 한다. %%FacesMessage%%를 위한 팩토리 메소드는 우리가 직접 작성할 것이 아니라 표준 API의 일부여야 한다고 생각한다. JSF의 차기 버전에서는 이것이 반영되기를 희망한다.))