/* * Copyright (C) 2010-2017 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * * Akvo FLOW is free software: you can redistribute it and modify it under the terms of * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, * either version 3 of the License or any later version. * * Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License included below for more details. * * The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>. */ package com.gallatinsystems.framework.rest; import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gallatinsystems.framework.rest.exception.RestException; /** * Base class for any REST apis. It handles via a template method the following actions: set the * response type (based on the mode set via servlet init params) read the request (delegating to * subclasses) validating the request writing the response (delegating to subclasses) or writing the * error to the response Servlets that descend from this class can handle both POSTs and GETs * * @author Christopher Fagiani */ public abstract class AbstractRestApiServlet extends HttpServlet { private static Logger log = Logger.getLogger(AbstractRestApiServlet.class .getName()); private static final long serialVersionUID = -8553345034709944772L; public static final String XML_MODE = "XML"; public static final String JSON_MODE = "JSON"; public static final String XHTML_MODE = "XHTML"; public static final String PLAINTEXT_MODE = "TEXT"; private String mode; private ThreadLocal<HttpServletRequest> requests; private ThreadLocal<HttpServletResponse> responses; public void setMode(String mode) { this.mode = mode; } public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { executeRequest(req, resp); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { executeRequest(req, resp); } /** * handles the incoming request by first binding the request/response to thread local variables * (so this servlet can handle multiple simultaneous requests). It will then parse the http * request into a RestRequest and pass that to the handleRequest abstract method. * * @param req * @param resp */ private void executeRequest(HttpServletRequest req, HttpServletResponse resp) { try { checkThreadLocal(); resp.setCharacterEncoding("UTF-8"); // bind request/response to thread local requests.set(req); responses.set(resp); setContentType(resp); // convert the http request to our RestRequest and validate it RestRequest restReq = convertRequest(); restReq.validate(); RestResponse restResp = handleRequest(restReq); // if we're here, we're ok writeOkResponse(restResp); } catch (RestException e) { // if handleRequest threw an execption, handle it writeErrorResponse(e.getErrors(), resp); } catch (Throwable e) { // we get here if we get some unexpected exception that does not // derive from RestException writeErrorResponse(null, resp); log.log(Level.SEVERE, "Could not execute rest request", e); } finally { // null out the request/response objects so we don't leak memory requests.set(null); responses.set(null); } } /** * converts a servlet request to a RestRequest * * @return * @throws Exception */ protected abstract RestRequest convertRequest() throws Exception; /** * performs the api specific action. If the servlet can support multiple api methods, this * method should look a the action field in the http request and delegate appropriately. * * @param req * @return * @throws Exception */ protected abstract RestResponse handleRequest(RestRequest req) throws Exception; /** * writes non-error response to the the output stream * * @param restResponse * @param resp * @throws Exception */ protected abstract void writeOkResponse(RestResponse resp) throws Exception; /** * sets the content type of the response based on the value in web.xml. If no value is * specified, we assume plaintext * * @param resp */ private void setContentType(HttpServletResponse resp) { if (XML_MODE.equalsIgnoreCase(mode)) { resp.setContentType("text/xml;charset=utf-8"); } else if (JSON_MODE.equalsIgnoreCase(mode)) { resp.setContentType("application/json;charset=utf-8"); } else if (XHTML_MODE.equalsIgnoreCase(mode)) { resp.setContentType("application/xhtml+xml"); } else { resp.setContentType("text/plain"); } } /** * writes the contents of the RestError to the response * * @param err * @param resp */ protected void writeErrorResponse(List<RestError> errs, HttpServletResponse resp) { try { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // TODO: error should honor content type (i.e. xml, json or text) if (errs != null) { for (RestError err : errs) { resp.getWriter().print(err.toString() + "\n"); } } else { resp.getWriter().print(new RestError()); } } catch (IOException e) { log.log(Level.SEVERE, "Could not write to servlet response object", e); } } /** * gets the thread local request * * @return */ protected HttpServletRequest getRequest() { return requests.get(); } /** * gets the thread local response * * @return */ protected HttpServletResponse getResponse() { return responses.get(); } /** * initializes thread local objects if they're null */ private void checkThreadLocal() { if (requests == null) { requests = new ThreadLocal<HttpServletRequest>(); } if (responses == null) { responses = new ThreadLocal<HttpServletResponse>(); } } }