com.aipo.container.protocol.AipoDataServiceServlet.java Source code

Java tutorial

Introduction

Here is the source code for com.aipo.container.protocol.AipoDataServiceServlet.java

Source

/*
 * Aipo is a groupware program developed by TOWN, Inc.
 * Copyright (C) 2004-2015 TOWN, Inc.
 * http://www.aipo.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aipo.container.protocol;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.cayenne.access.DataContext;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.shindig.auth.AipoOAuth2SecurityToken;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.common.Nullable;
import org.apache.shindig.common.servlet.HttpUtil;
import org.apache.shindig.protocol.ApiServlet;
import org.apache.shindig.protocol.ContentTypes;
import org.apache.shindig.protocol.ContentTypes.InvalidContentTypeException;
import org.apache.shindig.protocol.DataCollection;
import org.apache.shindig.protocol.DataServiceServlet;
import org.apache.shindig.protocol.ResponseItem;
import org.apache.shindig.protocol.RestHandler;
import org.apache.shindig.protocol.RestfulCollection;
import org.apache.shindig.protocol.conversion.BeanConverter;
import org.apache.shindig.protocol.multipart.FormDataItem;
import org.apache.shindig.protocol.multipart.MultipartFormParser;
import org.json.JSONObject;

import com.aipo.orm.Database;
import com.aipo.orm.model.security.TurbineUser;
import com.aipo.orm.service.TurbineUserDbService;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Inject;

/**
 * @see DataServiceServlet
 */
public class AipoDataServiceServlet extends ApiServlet {

    private static final long serialVersionUID = -2013432634263695142L;

    private static final Logger LOG = Logger.getLogger(AipoDataServiceServlet.class.getName());

    public static final Set<String> ALLOWED_CONTENT_TYPES = new ImmutableSet.Builder<String>()
            .addAll(ContentTypes.ALLOWED_MULTIPART_CONTENT_TYPES).addAll(ContentTypes.ALLOWED_JSON_CONTENT_TYPES)
            .addAll(ContentTypes.ALLOWED_XML_CONTENT_TYPES).addAll(ContentTypes.ALLOWED_ATOM_CONTENT_TYPES)
            .addAll(ImmutableSet.of("application/x-www-form-urlencoded")).build();

    protected static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override";

    private MultipartFormParser formParser;

    protected TurbineUserDbService turbineUserDbService;

    @Inject
    void setMultipartFormParser(MultipartFormParser formParser) {
        this.formParser = formParser;
    }

    @Inject
    void setTurbineUserDbService(TurbineUserDbService turbineUserDbService) {
        this.turbineUserDbService = turbineUserDbService;
    }

    @Override
    protected void doGet(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws ServletException, IOException {
        executeRequest(servletRequest, servletResponse);
    }

    @Override
    protected void doPut(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws ServletException, IOException {
        try {
            checkContentTypes(ALLOWED_CONTENT_TYPES, servletRequest.getContentType());
            executeRequest(servletRequest, servletResponse);
        } catch (ContentTypes.InvalidContentTypeException icte) {
            sendError(servletResponse, new ResponseItem(HttpServletResponse.SC_BAD_REQUEST, icte.getMessage(),
                    AipoErrorCode.BAD_REQUEST.responseJSON()));
        }
    }

    @Override
    protected void doDelete(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws ServletException, IOException {
        executeRequest(servletRequest, servletResponse);
    }

    @Override
    protected void doPost(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws ServletException, IOException {
        try {
            checkContentTypes(ALLOWED_CONTENT_TYPES, servletRequest.getContentType());
            executeRequest(servletRequest, servletResponse);
        } catch (ContentTypes.InvalidContentTypeException icte) {
            sendError(servletResponse, new ResponseItem(HttpServletResponse.SC_BAD_REQUEST, icte.getMessage(),
                    AipoErrorCode.BAD_REQUEST.responseJSON()));
        }
    }

    /**
     * Actual dispatch handling for servlet requests
     */
    protected void executeRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws IOException {
        if (LOG.isLoggable(Level.FINEST)) {
            LOG.finest("Handling restful request for " + servletRequest.getPathInfo());
        }

        setCharacterEncodings(servletRequest, servletResponse);

        SecurityToken token = getSecurityToken(servletRequest);
        if (token == null) {
            sendError(servletResponse, AipoErrorCode.TOKEN_EXPIRED);
            return;
        }
        if (token instanceof AipoOAuth2SecurityToken) {
            AipoOAuth2SecurityToken securityToken = (AipoOAuth2SecurityToken) token;
            String viewerId = securityToken.getViewerId();
            String[] split = viewerId.split(":");
            String orgId = split[0];
            String userId = split[1];

            String currentOrgId = Database.getDomainName();
            if (currentOrgId == null) {
                try {
                    DataContext dataContext = Database.createDataContext(orgId);
                    DataContext.bindThreadObjectContext(dataContext);
                } catch (Throwable t) {
                    sendError(servletResponse, AipoErrorCode.INTERNAL_ERROR);
                    return;
                }
            } else if (!currentOrgId.equals(orgId)) {
                sendError(servletResponse, AipoErrorCode.INTERNAL_ERROR);
                return;
            }

            TurbineUser tuser = turbineUserDbService.findByUsername(userId);
            if (tuser == null) {
                sendError(servletResponse, AipoErrorCode.INVALID_UER);
                return;
            }

        }

        HttpUtil.setCORSheader(servletResponse,
                containerConfig.<String>getList(token.getContainer(), "gadgets.parentOrigins"));

        handleSingleRequest(servletRequest, servletResponse, token);
    }

    protected void sendError(HttpServletResponse servletResponse, AipoErrorCode code) throws IOException {
        String json = code.responseJSON().toString();

        servletResponse.setStatus(code.getStatus());
        servletResponse.setContentType("application/json; charset=utf8");
        OutputStream out = null;
        InputStream in = null;
        try {
            out = servletResponse.getOutputStream();
            in = new ByteArrayInputStream(json.getBytes("UTF-8"));
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            out.flush();
        } catch (Throwable t) {
            LOG.log(Level.WARNING, t.getMessage(), t);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    @Override
    protected void sendError(HttpServletResponse servletResponse, ResponseItem responseItem) throws IOException {
        int errorCode = responseItem.getErrorCode();
        Object response = responseItem.getResponse();
        if (errorCode < 0) {
            // Map JSON-RPC error codes into HTTP error codes as best we can
            // TODO: Augment the error message (if missing) with a default
            switch (errorCode) {
            case -32700:
            case -32602:
            case -32600:
                // Parse error, invalid params, and invalid request
                errorCode = HttpServletResponse.SC_BAD_REQUEST;
                break;
            case -32601:
                // Procedure doesn't exist
                errorCode = HttpServletResponse.SC_NOT_IMPLEMENTED;
                break;
            case -32603:
            default:
                // Internal server error, or any application-defined error
                errorCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
                break;
            }
        }

        String json = "{}";
        if (response != null && response instanceof JSONObject) {
            json = response.toString();
        } else {
            switch (errorCode) {
            case 400:
                json = AipoErrorCode.BAD_REQUEST.responseJSON().toString();
                break;
            case 401:
                json = AipoErrorCode.TOKEN_EXPIRED.responseJSON().toString();
                break;
            case 403:
                json = AipoErrorCode.INVALID_UER.responseJSON().toString();
                break;
            case 404:
                json = AipoErrorCode.NOT_FOUND.responseJSON().toString();
                break;
            case 500:
                json = AipoErrorCode.INTERNAL_ERROR.responseJSON().toString();
                break;
            case 501:
                json = AipoErrorCode.NOT_IMPLEMENTED.responseJSON().toString();
                break;
            case 502:
                json = AipoErrorCode.BAD_GATEWAY.responseJSON().toString();
                break;
            case 503:
                json = AipoErrorCode.SERVICE_UNAVAILABLE.responseJSON().toString();
                break;
            case 504:
                json = AipoErrorCode.GATEWAY_TIMEOUT.responseJSON().toString();
                break;
            default:
                json = AipoErrorCode.INTERNAL_ERROR.responseJSON().toString();
                errorCode = 500;
                break;
            }
        }

        servletResponse.setStatus(errorCode);
        servletResponse.setContentType("application/json; charset=utf8");
        OutputStream out = null;
        InputStream in = null;
        try {
            out = servletResponse.getOutputStream();
            in = new ByteArrayInputStream(json.getBytes("UTF-8"));
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            out.flush();
        } catch (Throwable t) {
            LOG.log(Level.WARNING, t.getMessage(), t);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * Handler for non-batch requests.
     */
    protected void handleSingleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            SecurityToken token) throws IOException {

        // Always returns a non-null handler.
        RestHandler handler = getRestHandler(servletRequest);

        // Get Content-Type
        String contentType = null;

        try {
            // TODO: First implementation causes bug when Content-Type is
            // application/atom+xml. Fix is applied.
            contentType = ContentTypes.extractMimePart(servletRequest.getContentType());
        } catch (Throwable t) {
            // this happens while testing
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("Unexpected error : content type is null " + t.toString());
            }
        }

        // Get BeanConverter for Request payload.
        BeanConverter requestConverter = getConverterForRequest(contentType, null);

        // Get BeanConverter for Response body.
        BeanConverter responseConverter = getConverterForFormat(null);

        // Execute the request
        Map<String, FormDataItem> formItems = Maps.newHashMap();
        Map<String, String[]> parameterMap = loadParameters(servletRequest, formItems);
        Future<?> future = handler.execute(parameterMap, formItems, token, requestConverter);
        ResponseItem responseItem = getResponseItem(future);

        servletResponse.setContentType(responseConverter.getContentType());

        if (responseItem.getErrorCode() >= 200 && responseItem.getErrorCode() < 400) {
            Object response = responseItem.getResponse();

            if (response instanceof StreamContent) {
                InputStream is = null;
                OutputStream out = null;
                try {
                    StreamContent content = (StreamContent) response;
                    servletResponse.setContentType(content.getContentType());
                    if (content.getContentLength() != 0) {
                        servletResponse.setContentLength(content.getContentLength());
                    }
                    is = content.getInputStream();
                    out = servletResponse.getOutputStream();
                    int b;
                    while ((b = is.read()) != -1) {
                        out.write(b);
                    }
                } finally {
                    try {
                        if (out != null) {
                            out.close();
                        }
                    } catch (IOException ignore) {
                        // ignore
                    }
                    try {
                        if (is != null) {
                            is.close();
                        }
                    } catch (IOException ignore) {
                        // ignore
                    }
                }
                return;
            }

            // TODO: ugliness resulting from not using RestfulItem
            if (!(response instanceof DataCollection) && !(response instanceof RestfulCollection)) {
                response = ImmutableMap.of("entry", response);
            }

            // JSONP style callbacks
            String callback = (HttpUtil.isJSONP(servletRequest)
                    && ContentTypes.OUTPUT_JSON_CONTENT_TYPE.equals(responseConverter.getContentType()))
                            ? servletRequest.getParameter("callback")
                            : null;

            PrintWriter writer = servletResponse.getWriter();
            if (callback != null) {
                writer.write(callback + '(');
            }
            writer.write(responseConverter.convertToString(response));
            if (callback != null) {
                writer.write(");\n");
            }
        } else {
            sendError(servletResponse, responseItem);
        }
    }

    protected RestHandler getRestHandler(HttpServletRequest servletRequest) {
        // TODO Rework to allow sub-services
        String path = servletRequest.getPathInfo();

        // TODO - This shouldnt be on BaseRequestItem
        String method = servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE);
        if (method == null) {
            method = servletRequest.getMethod();
        }

        // Always returns a non-null handler.
        return dispatcher.getRestHandler(path, method.toUpperCase());
    }

    /*
     * Return the right BeanConverter to convert the request payload.
     */
    public BeanConverter getConverterForRequest(@Nullable String contentType, String format) {
        if (StringUtils.isNotBlank(contentType)) {
            return getConverterForContentType(contentType);
        } else {
            return getConverterForFormat(format);
        }
    }

    /**
     * Return BeanConverter based on content type.
     *
     * @param contentType
     *          the content type for the converter.
     * @return BeanConverter based on the contentType input param. Will default to
     *         JSON
     */
    protected BeanConverter getConverterForContentType(String contentType) {
        return ContentTypes.ALLOWED_ATOM_CONTENT_TYPES.contains(contentType) ? atomConverter
                : ContentTypes.ALLOWED_XML_CONTENT_TYPES.contains(contentType) ? xmlConverter : jsonConverter;
    }

    /**
     * Return BeanConverter based on format request parameter.
     *
     * @param format
     *          the format for the converter.
     * @return BeanConverter based on the format input param. Will default to JSON
     */
    protected BeanConverter getConverterForFormat(String format) {
        return ATOM_FORMAT.equals(format) ? atomConverter
                : XML_FORMAT.equals(format) ? xmlConverter : jsonConverter;
    }

    protected void checkContentTypes(Set<String> allowedContentTypes, String contentType)
            throws ContentTypes.InvalidContentTypeException {
        if (StringUtils.isEmpty(contentType)) {
            throw new InvalidContentTypeException("No Content-Type specified. One of "
                    + StringUtils.join(allowedContentTypes, ", ") + " is required");
        }
        contentType = ContentTypes.extractMimePart(contentType);
        if (allowedContentTypes.contains(contentType)) {
            return;
        }
        throw new InvalidContentTypeException("Unsupported Content-Type " + contentType + ". One of "
                + StringUtils.join(allowedContentTypes, ", ") + " is required");
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Map<String, String[]> loadParameters(HttpServletRequest servletRequest,
            Map<String, FormDataItem> formItems) {
        Map<String, String[]> parameterMap = new HashMap<String, String[]>();

        // requestParameter???
        Map<String, String[]> map = servletRequest.getParameterMap();
        for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String[] _value = (String[]) entry.getValue();
            String[] value = new String[_value.length];
            for (int i = 0; i < _value.length; i++) {
                value[i] = _value[i];
            }
            parameterMap.put(key, value);
        }

        if (servletRequest.getContentType() != null) {
            // Content-type?multipart/form-data??????
            if (ContentTypes.MULTIPART_FORM_CONTENT_TYPE
                    .equals(ContentTypes.extractMimePart(servletRequest.getContentType()))) {
                if (formParser.isMultipartContent(servletRequest)) {
                    Collection<FormDataItem> items = null;
                    try {
                        items = formParser.parse(servletRequest);
                    } catch (IOException e) {
                        // ignore
                    }
                    if (items != null) {
                        for (FormDataItem item : items) {
                            if (item.isFormField()) {
                                String value = item.getAsString();
                                String key = item.getFieldName();
                                if (value != null) {
                                    try {
                                        value = new String(value.getBytes("iso-8859-1"), "utf-8");
                                    } catch (UnsupportedEncodingException e) {
                                        // ignore
                                    }
                                }
                                String[] valueArray = { value };
                                if (parameterMap.containsKey(key)) {
                                    String[] preValue = parameterMap.get(key);
                                    valueArray = Arrays.copyOf(preValue, preValue.length + 1);
                                    valueArray[preValue.length] = value;
                                }
                                parameterMap.put(key, valueArray);
                            } else {
                                formItems.put(item.getFieldName(), item);
                            }
                        }
                    }
                }

            }

            // Content-type?application/x-www-form-urlencoded??????
            if ("application/x-www-form-urlencoded"
                    .equals(ContentTypes.extractMimePart(servletRequest.getContentType()))) {
                try {
                    List<NameValuePair> params = URLEncodedUtils
                            .parse(new URI("http://localhost:8080?" + getBodyAsString(servletRequest)), "UTF-8");
                    for (NameValuePair param : params) {
                        String[] valueArray = { param.getValue() };
                        if (parameterMap.containsKey(param.getName())) {
                            String[] preValue = parameterMap.get(param.getName());
                            valueArray = Arrays.copyOf(preValue, preValue.length + 1);
                            valueArray[preValue.length] = param.getValue();
                        }
                        parameterMap.put(param.getName(), valueArray);
                    }
                } catch (URISyntaxException e) {
                    // ignore
                }
            }
        }

        return parameterMap;
    }

    private String getBodyAsString(HttpServletRequest request) {
        if (request.getContentLength() == 0) {
            return "";
        }
        InputStream is = null;
        try {
            String line;
            StringBuilder sb = new StringBuilder();
            is = request.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            is.close();
            return sb.toString();
        } catch (IOException ioe) {
            // ignore
            return null;
        } finally {
            IOUtils.closeQuietly(is);
        }
    }
}