/**
* Copyright 2014 Daum Kakao Corp.
*
* Redistribution and modification in source or binary forms are not permitted without specific prior written permission.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kakao.rest;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.apache.http.HttpStatus;
import android.os.Bundle;
import android.os.Message;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kakao.APIErrorResult;
import com.kakao.Session;
import com.kakao.SessionCallback;
import com.kakao.exception.KakaoException;
import com.kakao.helper.ServerProtocol;
import com.kakao.http.HttpRequestTask;
import com.kakao.http.HttpResponseHandler;
import com.kakao.http.HttpTaskManager;
import com.kakao.http.KakaoAsyncHandler;
import com.ning.http.client.AsyncHttpClient.BoundRequestBuilder;
import com.ning.http.client.Request;
import com.ning.http.client.Response;
/**
* API 요청을 위한 HttpRequestTask로 세션 만료시 재갱신하여 재요청해주고,
* 서버 에러가 발생한 경우 APIErrorResult 객체를 반환하도록 한다.
* @param <T> : 요청이 성공한 경우 return 객체 type. HttpResponseHandler<T>, Class<T>
* @author MJ
*/
public class APIHttpRequestTask<T> extends HttpRequestTask<T> {
private final HttpResponseHandler<T> HttpResponseHandler;
public APIHttpRequestTask(final Request request, final HttpResponseHandler<T> httpResponseHandler, final Class<T> returnType) {
super(request, new APIAsyncHandler<T>(request, httpResponseHandler, returnType));
this.HttpResponseHandler = httpResponseHandler;
}
public static void checkSessionAndExecute(final APIHttpRequestTask requestTask, final HttpResponseHandler responseHandler) {
if (Session.getCurrentSession().isOpened())
HttpTaskManager.execute(requestTask);
else if (!requestAccessTokenWithRefreshToken(requestTask, responseHandler)) {
failedToRefreshAccessToken(requestTask, responseHandler, "session is closed before sending the request");
}
}
public static <T> void requestGet(final HttpResponseHandler<T> responseHandler, final Class<T> responseClass, final String url, final Bundle queryParams) {
final BoundRequestBuilder requestBuilder = HttpRequestTask.ASYNC_HTTP_CLIENT.prepareGet(url);
addTokenHeader(requestBuilder);
if(queryParams != null && !queryParams.isEmpty()) {
addQueryParams(requestBuilder, queryParams);
}
checkSessionAndExecute(new APIHttpRequestTask<T>(requestBuilder.build(), responseHandler, responseClass), responseHandler);
}
public static <T> void requestPost(final HttpResponseHandler<T> responseHandler, final Class<T> responseClass, final String url, final Bundle queryParams) {
final BoundRequestBuilder requestBuilder = HttpRequestTask.ASYNC_HTTP_CLIENT.preparePost(url);
addTokenHeader(requestBuilder);
if (queryParams != null && !queryParams.isEmpty()) {
addParams(requestBuilder, queryParams);
}
checkSessionAndExecute(new APIHttpRequestTask<T>(requestBuilder.build(), responseHandler, responseClass), responseHandler);
}
public static <T> void requestDelete(final HttpResponseHandler<T> responseHandler, final Class<T> responseClass, final String url, final Bundle queryParams) {
final BoundRequestBuilder requestBuilder = HttpRequestTask.ASYNC_HTTP_CLIENT.prepareDelete(url);
addTokenHeader(requestBuilder);
if(queryParams != null && !queryParams.isEmpty()) {
addQueryParams(requestBuilder, queryParams);
}
checkSessionAndExecute(new APIHttpRequestTask<T>(requestBuilder.build(), responseHandler, responseClass), responseHandler);
}
private static void failedToRefreshAccessToken(final APIHttpRequestTask requestTask, final HttpResponseHandler responseHandler, final String errorMsg) {
String requestUrl = null;
if(requestTask != null && requestTask.request != null)
requestUrl = requestTask.request.getUrl();
final APIErrorResult clientError = new APIErrorResult(requestUrl, errorMsg);
responseHandler.sendMessage(Message.obtain(responseHandler, NEED_TO_LOGIN, 0, 0, clientError));
}
private static class APIAsyncHandler<T> extends KakaoAsyncHandler<T> {
public APIAsyncHandler(final Request request, final HttpResponseHandler<T> httpResponseHandler, final Class<T> returnType) {
super(request, httpResponseHandler, returnType);
}
protected Void handleFailureHttpStatus(final Response response, final URI requestUri, final int httpStatusCode) throws IOException {
switch (httpStatusCode) {
case HttpStatus.SC_UNAUTHORIZED:
requestAccessTokenWithRefreshToken(new APIHttpRequestTask<T>(request, httpResponseHandler, returnType), httpResponseHandler);
return null;
case HttpStatus.SC_BAD_REQUEST:
case HttpStatus.SC_FORBIDDEN:
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
if (checkResponseBody(response)) return null;
final APIErrorResult result = new ObjectMapper().readValue(response.getResponseBody(request.getBodyEncoding()), APIErrorResult.class);
result.setRequestURL(requestUri == null ? null : requestUri.toString());
httpResponseHandler.sendMessage(Message.obtain(httpResponseHandler, ERROR, 0, 0, result));
return null;
default:
sendError(response, "not expected http status");
return null;
}
}
}
private static boolean requestAccessTokenWithRefreshToken(final APIHttpRequestTask requestTask, final HttpResponseHandler responseHandler) {
return Session.getCurrentSession().implicitOpen(new SessionCallback() {
@Override
public void onSessionOpened() {
HttpTaskManager.execute(requestTask.updateSession());
}
@Override
public void onSessionClosed(final KakaoException exception) {
failedToRefreshAccessToken(requestTask, responseHandler, "session is closed during refreshing token for the request");
}
});
}
private HttpRequestTask updateSession() {
final List<String> token = new ArrayList<String>();
token.add(getTokenAuthHeaderValue());
request.getHeaders().put(ServerProtocol.AUTHORIZATION_HEADER_KEY, token);
return this;
}
protected void preRequest() {
HttpResponseHandler.sendMessage(Message.obtain(HttpResponseHandler, -1, 0, 0));
}
protected void failedRequest(Exception e) {
HttpResponseHandler.sendMessage(Message.obtain(HttpResponseHandler, ERROR, 0, 0, e));
}
public static void addTokenHeader(final BoundRequestBuilder requestBuilder) {
final Entry<String,String> entry = KA_HEADER.entrySet().iterator().next();
requestBuilder.addHeader(entry.getKey(), entry.getValue());
requestBuilder.addHeader(ServerProtocol.AUTHORIZATION_HEADER_KEY, getTokenAuthHeaderValue());
}
private static String getTokenAuthHeaderValue() {
final StringBuilder autorizationValue = new StringBuilder();
autorizationValue.append(ServerProtocol.AUTHORIZATION_BEARER).append(ServerProtocol.AUTHORIZATION_HEADER_DELIMITER).append(Session.getCurrentSession().getAccessToken());
return autorizationValue.toString();
}
}