/*
* Copyright (c) 2001-2013 newgxu.cn <the original author or authors>.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package cn.newgxu.ng.api;
import java.io.IOException;
import java.io.ObjectInputStream.GetField;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestHandler;
import com.sun.mail.util.QEncoderStream;
import cn.newgxu.bbs.common.util.Util;
import cn.newgxu.bbs.domain.Forum;
import cn.newgxu.bbs.domain.Reply;
import cn.newgxu.bbs.domain.Topic;
import cn.newgxu.bbs.domain.user.User;
import cn.newgxu.bbs.service.UserService;
import cn.newgxu.jpamodel.ObjectNotFoundException;
import cn.newgxu.ng.util.RegexUtils;
/**
*
* @author longkai
* @email im.longkai@gmail.com
* @since 2013-5-23
* @version 0.1
*/
@Controller("RESTAPI")
public class RESTAPIServlet implements HttpRequestHandler {
private static final Logger L = LoggerFactory.getLogger(RESTAPIServlet.class);
private static final int GET = 1;
private static final int POST = 2;
private static final int PUT = 3;
private static final int DELETE = 4;
private static final int JSON = 1;
private static final int JSONP = 2;
/** 请求列表的最大项目数 */
private static final int MAX_LIST_COUNT = 50;
private static final String MSG = "msg";
private static final String STATUS = "status";
private static Pattern pattern = Pattern.compile("(\\d{1,})(\\..*$)?");
private static Matcher matcher;
@Inject
private UserService userService;
/**
* 提取uri中的id
* @param uri
* @return 解析后的id索引,如果没有(代表操作的是一个列表)返回-1
*/
private static final int resolveId(String uri) {
matcher = pattern.matcher(uri);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
} else {
return -1;
}
}
/**
* 解析HTTP method,并且返回相应的method code供switch!若方法不受支持则抛出异常。变长参数为空意味着所有的方法都支持!
* @param request
* @return 方法的指代整形值
*/
private static final int supportedMethod(HttpServletRequest request, int...methods) {
String method = request.getMethod();
int code = -1;
if (method.equalsIgnoreCase("GET")) {
code = GET;
} else if (method.equalsIgnoreCase("POST")) {
code = POST;
} else if (method.equalsIgnoreCase("PUT")) {
code = PUT;
} else if (method.equalsIgnoreCase("DELETE")) {
code = DELETE;
} else {
throw new IllegalArgumentException("不存在[HTTP Method = " + method + "]这个方法!");
}
int in = 0;
if (methods.length == 0) {
in = 4;
} else {
for (int i = 0; i < methods.length; i++) {
if (methods[i] == code) {
in++;
break;
}
}
}
if (in == 0) {
throw new UnsupportedOperationException("Unsupported HTTP Method: " + method);
}
return code;
}
/**
* 将请求的参数转换为整形,若为空或者空串以及转型错误会抛出相应异常。
*
* @param paramName 参数名
* @param request
* @return 整形值
*/
private static final int convertParam2Int(String paramName, HttpServletRequest request) {
int result = Integer.MIN_VALUE;
String str = request.getParameter(paramName);
Assert.hasLength(str, paramName + " 参数不能为空!");
try {
result = Integer.parseInt(str);
} catch (Exception e) {
L.error("转换参数:{} 为整形时出现异常!", e);
throw new IllegalArgumentException(String.format("请求参数[%s]转换异常!", paramName), e);
}
// 拦截一下请求的列表项目大小
if (paramName.equalsIgnoreCase("count")) {
if (result > MAX_LIST_COUNT || result < 0) {
throw new IllegalArgumentException("请求列表参数count为负或者大于" + MAX_LIST_COUNT);
}
}
return result;
}
/** 返回何种视图 */
private static final int whichViewType(HttpServletRequest request) {
String callback = request.getParameter("callback");
if (callback != null && callback.length() > 0 && request.getMethod().equalsIgnoreCase("GET")) {
return JSONP;
}
return JSON;
}
/** 返回视图 */
private static final void render(HttpServletRequest request, HttpServletResponse response, String json) throws IOException {
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
if (whichViewType(request) == JSONP) {
String callback = request.getParameter("callback");
json = "try{" + callback + "(" + json + ")}catch(e){}";
}
writer.write(json);
writer.flush();
writer.close();
}
@Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String result = null;
try {
result = dispatcher(request, response);
} catch (Throwable e) {
L.error("APIServlet", e);
Throwable cause = e.getCause();
result = String.format("{\"status\":0,\"msg\":\"%s\",\"reason\":\"%s\"}", e.getMessage(), cause != null ? cause.getMessage() : "对不起,我们没有能收集足够的错误信息,请您稍后再试!");
}
render(request, response, result);
}
private static String dispatcher(HttpServletRequest request, HttpServletResponse response) throws JSONException, IOException {
String uri = request.getRequestURI();
if (RegexUtils.contains(uri, "/topics")) {
int id = resolveId(uri);
if (id < 0) {
// 操作集合
int type = convertParam2Int("type", request);
int count = convertParam2Int("count", request);
switch (type) {
case R.topic.LATEST_TOPICS:
return latestTopics(request, response, count);
case R.topic.REFRESH:
return refreshTopics(request, count);
case R.topic.FETCH_MORE:
return fetchMoreTopics(request, count);
case R.topic.FORUM_LATEST_TOPICS:
int fid = convertParam2Int("fid", request);
return latestTopics(request, response, fid, count);
case R.topic.FORUM_REFRESH:
return refreshForumTopisc(request, response, count);
case R.topic.FORUM_FETCH_MORE:
return fetchMoreTopics(request, response, count);
case R.topic.CLASSICS:
return classics(request, response, count);
default:
throw new IllegalArgumentException("对不起,没有[type=" + type + "]这个选项!");
}
} else {
// 操作单个对象
int method = supportedMethod(request);
switch (method) {
case GET:
try {
Topic topic = Topic.get(id);
return mappingTopic2Json(topic);
} catch (ObjectNotFoundException e) {
throw new IllegalArgumentException(String.format("没有找到[id=%d]的帖子!", id), e);
}
default:
break;
}
}
} else if (RegexUtils.contains(uri, "/replies")) {
// 对回复进行操作
int id = resolveId(uri);
if (id < 0) {
// 列表项
int type = convertParam2Int("type", request);
int count = convertParam2Int("count", request);
switch (type) {
case R.reply.LATEST:
return latestReplies(request, response, count);
default:
break;
}
}
} else if (RegexUtils.contains(uri, "/forums")) {
int id = resolveId(uri);
if (id < 0) {
// 列表项
} else {
int method = supportedMethod(request);
switch (method) {
case GET:
return forum2Json(id);
default:
break;
}
}
} else if (RegexUtils.contains(uri, "/users")) {
if (uri.contains("/users/login")) {
return user2Json(request);
}
int id = resolveId(uri);
if (id < 0) {
// 集合操作
} else {
int method = supportedMethod(request);
switch (method) {
case GET:
return user2Json(id);
default:
break;
}
}
}
return null;
}
/** 获取全论坛最最新的帖子。 */
private static String latestTopics(HttpServletRequest request, HttpServletResponse response, int count) throws JSONException, IOException {
supportedMethod(request, GET);
List<Topic> topics = Topic.getLatesTopics(count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String mappingTopic2Json(Topic t) throws JSONException {
Map<String, Object> map = new HashMap<String, Object>();
map.put(R.topic.ADDED_TIME, t.getCreationTime().getTime());
map.put(R.topic.AUTHOR_NICK, t.getUser().getNick());
map.put(R.topic.CLICK_TIMES, t.getClickTimes());
map.put(R.ID, t.getId());
// map.put(R.topic.LAST_REPLIED_TIME, t.getReplyTime().getTime());
// map.put(R.topic.LAST_REPLIED_USER_NICK, t.getReplyUser().getNick());
map.put(R.topic.REPLIED_TIMES, t.getReplyTimes());
map.put(R.topic.TITLE, t.getTitle());
map.put(R.topic.FORUM, t.getForum().getName());
map.put(R.topic.CONTENT, t.resolveContent());
map.put("author", R.resolveUser2Map(t.getUser()));
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topic", map);
return json.toString();
}
/** 刷新最新帖子列表,需要客户端传递一个本地最新的topic id标识 */
private static String refreshTopics(HttpServletRequest request, int count) throws JSONException {
supportedMethod(request, GET);
int topTid = convertParam2Int("top_tid", request);
List<Topic> topics = Topic.refresh(topTid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String fetchMoreTopics(HttpServletRequest request, int count) throws JSONException {
supportedMethod(request, GET);
int lastTid = convertParam2Int("last_tid", request);
List<Topic> topics = Topic.fetchMore(lastTid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
/** 查看某帖子的最新评论 */
private static String latestReplies(HttpServletRequest request, HttpServletResponse response, int count) throws JSONException {
supportedMethod(request, GET);
int lastRid = convertParam2Int("last_rid", request);
int tid = convertParam2Int("tid", request);
List<Reply> replies = Reply.fetchReplies(tid, lastRid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>(replies.size());
Map<String, Object> m = null;
for (Reply r : replies) {
m = R.resolveReply2Map(r);
data.add(m);
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("replies", data);
return json.toString();
}
private static String latestTopics(HttpServletRequest request, HttpServletResponse response, int fid, int count) throws JSONException {
supportedMethod(request, GET);
List<Topic> topics = Topic.fetchLatestTopics(fid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String refreshForumTopisc(HttpServletRequest request, HttpServletResponse response, int count) throws JSONException {
supportedMethod(request, GET);
int topTid = convertParam2Int("top_tid", request);
int fid = convertParam2Int("fid", request);
List<Topic> topics = Topic.refresh(fid, topTid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String fetchMoreTopics(HttpServletRequest request, HttpServletResponse response, int count) throws JSONException {
supportedMethod(request, GET);
int fid = convertParam2Int("fid", request);
int lastTid = convertParam2Int("last_tid", request);
List<Topic> topics = Topic.fetchMore(fid, lastTid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String classics(HttpServletRequest request, HttpServletResponse response, int count) throws JSONException {
supportedMethod(request, GET);
int fid = convertParam2Int("fid", request);
List<Topic> topics = Topic.fetchClassics(fid, count);
List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
for (Topic t : topics) {
data.add(R.resolveTopic2Map(t));
}
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("topics", data);
return json.toString();
}
private static String forum2Json(int fid) throws JSONException {
Forum f = null;
try {
f = Forum.get(fid);
} catch (ObjectNotFoundException e) {
L.error("forum not found!", e);
throw new IllegalArgumentException("对不起,您要找的forum不存在啊- -");
}
Map<String, Object> m = R.resolveForum2Map(f);
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("forum", m);
return json.toString();
}
private static final String user2Json(int uid) throws JSONException {
User u = null;
try {
u = User.get(uid);
} catch (ObjectNotFoundException e) {
L.error("user not found @id = " + uid, e);
throw new IllegalArgumentException("对不起,您要找的用户不存在啊- -");
}
Map<String, Object> m = R.resolveUser2Map(u);
JSONObject json = new JSONObject();
json.put(STATUS, 1);
json.put("user", m);
return json.toString();
}
private static final String user2Json(HttpServletRequest request) {
String username = request.getParameter("username");
User u;
try {
u = User.getByUsername(username);
} catch (ObjectNotFoundException e) {
return "{\"status\":0}";
}
String password = request.getParameter("password");
if (u == null || !u.getPassword().equals(Util.hash(password))) {
return "{\"status\":0}";
} else {
Map<String, Object> map = R.resolveUser2Map(u);
map.put("status", 1);
return new JSONObject(map).toString();
}
}
}