Fenriswolf 程式筆記

奮利斯狼的地盤,小綿羊勿入

CXF ExceptionMapper

在實作 Restful Services Exception Handling 需支援 L10N,簡單的做法是把 exception message 轉成相對應的對錯訊息。一個好的設計原則是讓程式不要跟 CXF 或特定的 framework 綁死,才能讓程式有更高的可攜性及重用性。因此我不希望在 API 上直接 return javax.ws.rs.core.Response 或 throw javax.ws.rs.WebApplicationException。因此需要自訂 ExceptionMapper 來支援我所要的功能

1. 先寫一個簡單的程式
Calculator.java

@Path("/calculator")
public interface Calculator {
    @GET
    @Path("/divide/{a}/{b}")
    public double divide(@PathParam("a")double a, @PathParam("b")double b) 
        throws CalculatorException;
}

CalculatorImpl.java

public class CalculatorImpl implements Calculator {
    @Override
    public double divide(double a, double b) throws CalculatorException {
        if(b == 0) {
            throw new CalculatorException("error.illegalargument");
        }
        
        return a / b;
    }
}

error.properties

error.illegalargument=The argument is illegal

這個例子只有檢查除數是否為 0,0 則丟出一個 CalculatorException
在 new CalculatorException 時傳入的 message 會對應到 properties file 裡的 key
要在 method 宣告明確的 exception type,client site 會根據 exception type 來決定正確的 ResponseExceptionMapper

2. 定義 Server site ExceptionMapper

public class ServerExceptionMapper implements ExceptionMapper<CalculatorException> {
    @Context 
    private MessageContext mc; 

    @Override
    public Response toResponse(CalculatorException exception) {
        ResponseBuilder builder = Response.status(Status.INTERNAL_SERVER_ERROR);
        builder.entity(generateErrorMessage(exception));
        return builder.build();
    }

    private String generateErrorMessage(CalculatorException exception) {
        HttpServletRequest request = mc.getHttpServletRequest();
        String errorKey = exception.getMessage();
        
        Locale locale = request.getLocale();
        ResourceBundle rb = ResourceBundle.getBundle("com.fw.cxf.error", locale);
        String message = rb.getString(errorKey);

        return message;
    }
}

a. 自訂的 ExceptionMapper 需要 implement javax.ws.rs.ext.ExceptionMapper 並指定對應的 exception type
b. Override toResponse method
c. 建立 ResponseBuilder,指定 http status code,status code 要大於 400,client site 才會判斷這是一個 exception。在 builder 裡的 entity 可以放任意的 object,這裡只有一個 L10N message,CXF parser 會把 entity 轉成 json 或 xml format

在正式的環境中應該把整個 exception 也放進去便於在 client site 轉回原來的 exception,但是 JAXB 無法 marshall Exception,要另外處理

3. 定義 Client site ExceptionMapper

public class ClientExceptionMapper implements ResponseExceptionMapper<CalculatorException> {
    public CalculatorException fromResponse(Response r) {
        InputStream is = (InputStream)r.getEntity();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuffer message = new StringBuffer();
        String line = null;
        
        try {
            while((line = br.readLine()) != null) {
                message.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return new CalculatorException(message.toString());
    }
}

ExceptionMapper 的做法跟 server site 類似,只是改成 implement org.apache.cxf.jaxrs.client.ResponseExceptionMapper
Client site 會根據所 call 的 method 找出 exception type,當 http status code 大於 400 時,則以對應的 ResponseExceptionMapper 來處理
這個範例是把 message 再傳入一個新的 CalculatorException 後丟出

4. 設定 Server

JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
ResourceProvider provider = new SingletonResourceProvider(new CalculatorImpl());
sf.setResourceClasses(Calculator.class);
sf.setResourceProvider(Calculator.class, provider);
sf.setAddress("http://localhost:8080/");        
sf.setProvider(new ServerExceptionMapper());

sf.create();

執行一個簡單的 resful server 是用 JAXRSServerFactoryBean 並傳入自訂的 ExceptionMapper
CXF 定義了多種的 provider 可供 user 使用,包括 MessageBodyReader、MessageBodyWriter、PhaseInterceptor 及 ExceptionMapper

5. 設定 Client

String url = "http://localhost:8080";
List<Object> providers = new ArrayList<Object>();
providers.add(new ClientExceptionMapper());

Calculator proxy = JAXRSClientFactory.create(url, Calculator.class, providers);
System.out.println(proxy.divide(4, 2));
System.out.println(proxy.divide(4, 0));

如果 client site 程式不是由 CXF 所寫就只能依據 http response status code 及內容來解析結果
但 CXF 提供了 proxy 的做法可讓 user 不需要處理 http request 和 response,這裡只要傳入自訂的 ResponseExceptionMapper 就好了

6. 測試
執行 http://localhost:8080/calculator/divide/4/0 的結果

The argument is illegal

執行 RestfulClient.java 的結果

2.0
Exception in thread "main" com.fw.cxf.CalculatorException: The argument is illegal
	at com.fw.cxf.ClientExceptionMapper.fromResponse(ClientExceptionMapper.java:27)
	at com.fw.cxf.ClientExceptionMapper.fromResponse(ClientExceptionMapper.java:1)
	at org.apache.cxf.jaxrs.client.ClientProxyImpl.checkResponse(ClientProxyImpl.java:226)
	at org.apache.cxf.jaxrs.client.ClientProxyImpl.handleResponse(ClientProxyImpl.java:453)
	at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:445)
	at org.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:177)
	at $Proxy36.divide(Unknown Source)
	at com.fw.cxf.RestfulClient.main(RestfulClient.java:16)

雖然已經寫了很多程式,但這個範例還不完整,實際的狀況還要考慮以下2個問題
1. CalculatorException 可把參數帶入 messages
2. RestfulClient.java 所丟出的 exception stack trace 並不能完全反應 server site 的情況
 
 
執行環境
JDK 1.6.0_16
cxf 2.2.8

參考資料
Apache CXF — JAX-RS

程式下載
Calculator.java
CalculatorImpl.java
CalculatorException.java
ServerExceptionMapper.java
ClientExceptionMapper.java
RestfulServer.java
RestfulClient.java
error.properties

廣告

2012/03/21 - Posted by | Java Tool |

仍無迴響。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

w

連結到 %s

%d 位部落客按了讚: