2010년 9월 28일 화요일

CXF에서 Log4J 사용하기

Resources:
http://www.techper.net/2008/01/30/configuring-cxf-logging-to-go-through-log4j/
http://gtko.springnote.com/pages/4826007

CXF에서 사용하는 로그는 별도로 지정하지 않으면 디폴트로 java.util.logging.Logger를 사용한다.
CXF에서 log4j를 사용하려면 다음과 같이 지정해 주면 된다.

1. -D옵션을 이용하여 시스템 property로 지정한다.
-Dorg.apache.cxf.Logger=org.apache.cxf.common.logging.Log4jLogger

2. META-INF/cxf/org.apache.cxf.Logger 파일로 설정한다.
-D옵션을 설정하기 용이치 않으면 위의 클래스패스에 위치한 파일에 아래와 같이 설정한다.
org.apache.cxf.common.logging.Log4jLogger

일반적으로 -D 옵션을 이용하는 방법과 동일한 결과를 프로그래밍적으로 해결하기 위한 방법으로
System.setProperty("org.apache.cxf.Logger", "org.apache.cxf.common.logging.Log4jLogger");
와 같이 설정하여 처리하고자 한다면 정상적으로 작동하지 않을 것이다.
이는 Spring이 초기화 되는 시점에 CXF 관련 빈들도 초기화 하는데 위의 코드가 반드시 CXF 빈들보다 먼저 초기화 된다고 볼 수 없기 때문이다.

1 또는 2의 방법중에서 하나만 설정해도 정상적으로 동작한다.
특히나 Inbound/Outbound 메시지를 보고 싶다면 log4j 설정파일에서
INFO레벨로 로그를 설정해주면 된다.

<logger name="org.apache.cxf" additivity="false">
<level value="INFO" />
<appender-ref ref="File_Appender" />
</logger>

2010년 9월 27일 월요일

SuppressWarnings value값 지정

Resources:
- http://knol.google.com/k/suppresswarnings-annotation-in-java#
- http://download.oracle.com/javase/tutorial/java/javaOO/annotations.html


SuppressWarnings 의 값들은 Java에서 제공하는 표준 값들과 Eclipse와 같은 IDE에서 자체적으로 정의한 값들이 있다.
SuppressWarnings의 값으로 지정하면 IDE에서 해당 Warning이 발견되면 Warning을 무시한다.

JDK에서 정의한 값들은 아래와 같다.

For example, Sun JDK 1.5 shows:

* all - suppress all warnings from this code
* deprecation - suppress warnings from using deprecated code
* unchecked - suppress warnings from an unchecked call or an unchecked cast
* fallthrough - suppress warnings if a switch falls through without finding a valid case (and no default)
* path -
* serial - suppress warnings if a Serializable class does not define a serialVersionUID
* finally - suppress warnings from return within a finally (which will ignore return with the try)


And Sun JDK 1.6 adds:

* cast
* divzero - suppress warnings if integer divide by zero is detected
* empty
* overrides
* none


Eclipse는 JDK에서 정의한 값을 포함하여 아래의 값들을 사용할 수 있다.

The Eclipse warning values for Eclipse 3.3 are documented in the JDT docs.

* all - suppress all warnings
* boxing - suppress warnings relative to boxing/unboxing operations
* cast - suppress warnings relative to cast operations
* dep-ann - suppress warnings relative to deprecated annotation
* deprecation - suppress warnings relative to deprecation
* fallthrough - suppress warnings relative to missing breaks in switch statements
* finally - suppress warnings relative to finally block that don't return
* hiding - suppress warnings relative to locals that hide variable
* incomplete-switch - suppress warnings relative to missing entries in a switch statement (enum case)
* nls - suppress warnings relative to non-nls string literals
* null - suppress warnings relative to null analysis
* restriction - suppress warnings relative to usage of discouraged or forbidden references
* serial - suppress warnings relative to missing serialVersionUID field for a serializable class
* static-access - suppress warnings relative to incorrect static access
* synthetic-access - suppress warnings relative to unoptimized access from inner classes
* unchecked - suppress warnings relative to unchecked operations
* unqualified-field-access - suppress warnings relative to field access unqualified
* unused - suppress warnings relative to unused code



Examples

An example of specifying a single warning:
@SuppressWarnings("unchecked")
public void methodWithScaryWarnings() {
List rawList = new ArrayList();
List stringList = (List)rawList;
}

An example of using two warnings:
@SuppressWarnings({"unchecked","deprecation"})
public void methodWithScaryWarnings() {
callDeprecatedMethod();
}

2010년 9월 17일 금요일

Spring에서 Controller JUnit으로 테스트하기

작성중...

Resources:
http://www.coderanch.com/t/459840/Spring/Integration-testing-Spring-Annotation-based

Controller에 대한 JUnit테스트는 조금 복잡한다.
Controller를 제외한 Repository, Service, Component stereotype들은 Dependency Injection이 복잡하지 않다. 하지만 Controller는 서블릿 컨텍스트를 초기화하여 Injection해야 하기 때문에 여러가지로 복잡한 부분이 있다.
이러한 문제 때문에 webmvc 관련된 설정은 기타 스프링 설정 파일과는 별로의 폴더에 설정한다.

아래와 같은 컨트롤러가 개발되어 있다고 가정하자.


package mypackage;

@Controller("accountController")
@RequestMapping("/accountController.sh")
public class AccountController extends BaseController {
...  
  
  @RequestMapping(params = "method=list")
  public String listAccounts(HttpServletRequest request, 
   @ModelAttribute AccountCondition condition,
   BindingResult result1, ModelMap map) throws Exception {

    
    
    map.addAttribute("accountCondition", condition);
    
    return "/account/account_lst";
  }
}


이 컨트롤러를 JUnit으로 테스트 하려면 WebContextLoader가 필요하다.
Tomcat과 같은 서블릿 컨테이너에서 테스트가 실행되는 것이 아니기 때문에 이와 비슷한 기능을 하는 Mock Web Context가 필요하다.

package mypackage;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;

public class MockWebContextLoader extends AbstractContextLoader {

  public static final ServletContext SERVLET_CONTEXT = 
    new MockServletContext("/src/main/webapp", new FileSystemResourceLoader());
  
  private final static GenericWebApplicationContext webContext =
    new GenericWebApplicationContext();
      
  protected BeanDefinitionReader createBeanDefinitionReader(
    final GenericApplicationContext context) {
    return new XmlBeanDefinitionReader(context);  
  }

  @Override
  protected String getResourceSuffix() {
    System.out.println("getResourceSuffic()");
    return ".xml";
  }

  @Override
  public ApplicationContext loadContext(String... locations) throws Exception {
    SERVLET_CONTEXT.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, webContext);
    webContext.setServletContext(SERVLET_CONTEXT);
    createBeanDefinitionReader(webContext).loadBeanDefinitions(locations);
    AnnotationConfigUtils.registerAnnotationConfigProcessors(webContext);
    webContext.refresh();
    webContext.registerShutdownHook();
    return webContext;
  }
  
  public static WebApplicationContext getInstance() {
    return webContext;
  }
}


위 WebContextLoader에서 주의 깊게 살펴 볼 메서드는 loadContext다.
maven에서 web resource를 관리하는 폴더인 src/main/webapp에서 리소스를 읽어 오도록 설정되어 있다.

컨트롤러 테스트 클래스들은 위에서 작성한 MockWebContextLoader를 사용하는 부분이 공통적으로 필요하다.
아래의 클래스는 DispatchServlet을 초기화 시키는 부분이 포함되어 있다.
또한 MockHttpServletRequest와 MockHttpServletResponse를 생성하는 메서드도 포함하고 있다.

package mypackage;

import java.util.Map;

import javax.servlet.ServletException;

import org.junit.runner.RunWith;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import junit.framework.TestCase;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=MockWebContextLoader.class, 
  locations={"classpath*:/spring/context-*.xml", 
    "classpath*:/springmvc/common-servlet.xml"})
public class AbstractControllerTestSupport extends TestCase {

  private static DispatcherServlet dispatcherServlet;
  
  @SuppressWarnings("serial")
  public static DispatcherServlet getServletInstance() {
    if(dispatcherServlet == null) {
      dispatcherServlet = new DispatcherServlet() {
        protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
          return MockWebContextLoader.getInstance();
        }
      };
      
      try {
        dispatcherServlet.init(new MockServletConfig());
      } catch(ServletException ex) {
      }
    }
    return dispatcherServlet;
  }
  
  protected MockHttpServletRequest mockRequest(String method, String uri, Map params) {
    MockHttpServletRequest req = new MockHttpServletRequest(method, uri);
    
    if(params != null) {
      for(String key: params.keySet()) {
        req.addParameter(key, params.get(key));
      }
    }
    return req;
  }
  
  protected MockHttpServletResponse mockResponse() {
    return new MockHttpServletResponse();
  }
}


이제 실제로 컨트롤러를 테스트를 작성할 차례다.

package mypackage;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.Resource;

import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.BindingResultUtils;

import mypackage.AccountCondition;

public class AccountControllerTest extends AbstractControllerTestSupport {

  @Resource(name="accountController")
  AccountController accountController;
  
  @Test()
  public void testListAccounts() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest();
    MockHttpServletResponse response = new MockHttpServletResponse();
    
    
    AccountCondition condition = new AccountCondition();
//    Map map = request.getParameterMap();
    Map map = new HashMap();
    
    BeanPropertyBindingResult result1 = new BeanPropertyBindingResult(condition, "accountCondition");

    map.put(BindingResult.MODEL_KEY_PREFIX + "accountCondition", result1);
    
    condition.setQueryEmail("credemol@gmail.com");
    map.put("accountCondition", condition);

    ModelMap modelMap = new ModelMap();
    String mav = accountController.listAccounts(request, condition, result1, modelMap);
    
    Collection result = (Collection) modelMap.get("accountResult");
    
    for(Iterator it = result.iterator(); it.hasNext();  ) {
      Object obj = it.next();
      
      System.out.println("obj: " + obj);
    }
  }
}