org.collectionspace.chain.controller.WebUIRequest.java Source code

Java tutorial

Introduction

Here is the source code for org.collectionspace.chain.controller.WebUIRequest.java

Source

/* Copyright 2010 University of Cambridge
 * Licensed under the Educational Community License (ECL), Version 2.0. You may not use this file except in 
 * compliance with this License.
 *
 * You may obtain a copy of the ECL 2.0 License at https://source.collectionspace.org/collection-space/LICENSE.txt
 */
package org.collectionspace.chain.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.*;
import org.collectionspace.chain.csp.persistence.services.RefName.Tools;
import org.collectionspace.csp.api.ui.Operation;
import org.collectionspace.csp.api.ui.TTYOutputter;
import org.collectionspace.csp.api.ui.UIException;
import org.collectionspace.csp.api.ui.UIRequest;
import org.collectionspace.csp.api.ui.UISession;
import org.collectionspace.csp.api.ui.UIUmbrella;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebUIRequest implements UIRequest {
    private static final Logger log = LoggerFactory.getLogger(WebUIRequest.class);
    private static final String COOKIENAME = "CSPACESESSID";
    private Integer lifeInMins = 45;

    private int cacheMaxAgeSeconds = 0; // Default to no caching

    private HttpServletRequest request;
    private HttpServletResponse response;
    private String[] ppath, rpath = null;
    private boolean failure = false, secondary_redirect = false;
    private Exception failing_exception;
    private Operation operation_performed = Operation.READ;
    private Map<String, String> rargs = new HashMap<String, String>();
    private PrintWriter out = null;
    private OutputStream out_stream = null;//XXX make inputstream output method for blobs
    private String out_data = null; // We store to allow late changes to headers
    private byte[] out_binary_data = null;
    private InputStream out_input_stream = null;
    private String body; // XXX what if it's binary?
    private FileItemHeaders contentHeaders;
    private String contenttype;
    private byte[] bytebody;
    private String uploadName;
    private WebUIUmbrella umbrella;
    private WebUISession session;
    private boolean solidified = false;
    private Integer status = null;

    private void initRequest(UIUmbrella umbrella, HttpServletRequest request, HttpServletResponse response,
            List<String> p) throws IOException, UIException {
        this.request = request;
        this.response = response;
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (isMultipart) {
            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload();

            // Parse the request
            FileItemIterator iter;
            try {
                iter = upload.getItemIterator(request);
                while (iter.hasNext()) {
                    FileItemStream item = iter.next();
                    String name = item.getFieldName();
                    //InputStream stream = item.openStream();
                    if (item.isFormField()) {
                        //   System.out.println("Form field " + name + " with value "
                        //    + Streams.asString(stream) + " detected.");
                    } else {
                        //   System.out.println("File field " + name + " with file name "
                        //    + item.getName() + " detected.");
                        // Process the input stream
                        contentHeaders = item.getHeaders();
                        uploadName = item.getName();

                        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                        if (item != null) {
                            InputStream stream = item.openStream();
                            IOUtils.copy(stream, byteOut);
                            new TeeInputStream(stream, byteOut);

                        }
                        bytebody = byteOut.toByteArray();
                    }
                }
            } catch (FileUploadException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            body = IOUtils.toString(request.getInputStream(), "UTF-8");
        }

        this.ppath = p.toArray(new String[0]);
        if (!(umbrella instanceof WebUIUmbrella))
            throw new UIException("Bad umbrella");
        this.umbrella = (WebUIUmbrella) umbrella;
        session = calculateSessionId();

    }

    static byte[] streamToBytes(InputStream in) throws IOException {
        return IOUtils.toByteArray(in);
    }

    public WebUIRequest(UIUmbrella umbrella, HttpServletRequest request, HttpServletResponse response,
            Integer cookieLife) throws IOException, UIException {
        this.lifeInMins = cookieLife;

        List<String> p = new ArrayList<String>();
        for (String part : request.getPathInfo().split("/")) {
            if ("".equals(part))
                continue;
            p.add(part);
        }
        initRequest(umbrella, request, response, p);
    }

    public WebUIRequest(UIUmbrella umbrella, HttpServletRequest request, HttpServletResponse response)
            throws IOException, UIException {
        this.lifeInMins = 15;

        List<String> p = new ArrayList<String>();
        for (String part : request.getPathInfo().split("/")) {
            if ("".equals(part))
                continue;
            p.add(part);
        }
        initRequest(umbrella, request, response, p);
    }

    public WebUIRequest(UIUmbrella umbrella, HttpServletRequest request, HttpServletResponse response,
            Integer cookieLife, List<String> p) throws IOException, UIException {
        this.lifeInMins = cookieLife;
        initRequest(umbrella, request, response, p);
    }

    public WebUIRequest(UIUmbrella umbrella, HttpServletRequest request, HttpServletResponse response,
            List<String> p) throws IOException, UIException {
        this.lifeInMins = 15;
        initRequest(umbrella, request, response, p);
    }

    private WebUISession calculateSessionId() throws UIException {

        Cookie[] cookies = request.getCookies();
        if (cookies == null)
            cookies = new Cookie[0];
        for (Cookie cookie : cookies) {
            if (!COOKIENAME.equals(cookie.getName()))
                continue;
            WebUISession session = umbrella.getSession(cookie.getValue());
            if (session != null) {
                return session;
            } else {
                System.err
                        .println("Could not get session from CSPACESESSID cookie with value: " + cookie.getValue());
            }
        }
        // No valid session: make our own
        return umbrella.createSession();
    }

    // XXX expire sessions
    private void setSession() {
        //if(session.isOld())
        //   return; // No need to reset session

        Cookie cookie = new Cookie(COOKIENAME, session.getID());
        cookie.setPath("/");//XXX should be /chain - so either need to have a parameter in cspace-config or try and ask tomcat who we are
        cookie.setMaxAge(60 * lifeInMins);
        response.addCookie(cookie);
    }

    // NOTE No changes to solidified stuff can happen after you get the TTY outputter
    @Override
    public TTYOutputter getTTYOutputter() throws UIException {
        try {
            WebTTYOutputter tty = new WebTTYOutputter(response);
            solidify(false);
            out = tty.getWriter();
            return tty;
        } catch (IOException e) {
            throw new UIException("Cannot create response PrintWriter", e);
        }
    }

    @Override
    public String[] getPrincipalPath() throws UIException {
        return ppath;
    }

    @Override
    public void setFailure(boolean isit, Exception why) throws UIException {
        failure = isit;
        failing_exception = why;
    }

    @Override
    public void setOperationPerformed(Operation op) throws UIException {
        operation_performed = op;
    }

    @Override
    public void setRedirectPath(String[] in) throws UIException {
        rpath = in;
        secondary_redirect = false;
    }

    @Override
    public void setSecondaryRedirectPath(String[] in) throws UIException {
        rpath = in;
        secondary_redirect = true;
    }

    @Override
    public void deleteRedirectArgument(String key) throws UIException {
        rargs.remove(key);
    }

    @Override
    public String getRedirectArgument(String key) throws UIException {
        return rargs.get(key);
    }

    @Override
    public void setRedirectArgument(String key, String value) throws UIException {
        rargs.put(key, value);
    }

    @Override
    public String getRequestArgument(String key) throws UIException {
        String param = request.getParameter(key);
        if (param != null)
            try {
                param = new String(param.getBytes("8859_1"), "UTF8");
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                param = request.getParameter(key);
            }

        return param;
    }

    @Override
    public Set<String> getAllRequestArgument() throws UIException {
        Set<String> params = new HashSet<String>();
        Enumeration<String> e = request.getParameterNames();
        while (e.hasMoreElements()) {
            String name = e.nextElement();
            params.add(name);
        }
        return params;
    }

    @Override
    public Operation getRequestedOperation() throws UIException {
        String method = request.getMethod();
        if ("POST".equals(method))
            return Operation.CREATE;
        else if ("PUT".equals(method))
            return Operation.UPDATE;
        else if ("DELETE".equals(method))
            return Operation.DELETE;
        else if ("GET".equals(method))
            return Operation.READ;
        return Operation.READ;
    }

    private void setSuccessStatus() {
        switch (operation_performed) {
        case CREATE:
            response.setStatus(201);
            break;
        default:
            response.setStatus(200);
        }
    }

    @Override
    public int getCacheMaxAgeSeconds() {
        return cacheMaxAgeSeconds;
    }

    @Override
    public void setCacheMaxAgeSeconds(int cacheMaxAgeSeconds) {
        this.cacheMaxAgeSeconds = cacheMaxAgeSeconds;
    }

    private String aWhileAgoAsExpectedByExpiresHeader() {
        SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss zzz");
        Date a_while_ago = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
        return format.format(a_while_ago);
    }

    private void close() {
        if (out != null)
            out.close();
        if (out_stream != null)
            try {
                out_stream.close();
            } catch (IOException e) {
                // Deliberate no-op: we can't really do much else
            }
        out = null;
    }

    public void solidify(boolean close) throws UIException {
        try {
            // Need to handle caching before we deal with the binary output. We have to add headers before
            // we write all the data. 
            if (cacheMaxAgeSeconds <= 0) {
                /*
                 * By default, we disable caching for now (for IE). We probably
                 * want to be cleverer at some point. XXX
                 */
                response.addHeader("Pragma", "no-cache");
                response.addHeader("Last-Modified", aWhileAgoAsExpectedByExpiresHeader());
                response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate");
                response.addHeader("Cache-Control", "post-check=0, pre-check=0");
            } else {
                // Create a cache header per the timeout requested (usu. by the individual request handler)
                response.addHeader("Cache-Control", "max-age=" + Integer.toString(cacheMaxAgeSeconds));
            }

            /* End of cacheing stuff */
            if (out_data != null) {
                out = response.getWriter();
                out.print(out_data);
            } else if (out_binary_data != null) {
                out_stream = response.getOutputStream();
                out_stream.write(out_binary_data);
            } else if (out_input_stream != null) {
                out_stream = response.getOutputStream();
                IOUtils.copy(out_input_stream, out_stream);
            }

            if (solidified) {
                if (close) {
                    close();
                }
                return;
            }

            solidified = true;
            if (failure) {
                // Failed
                int status = getStatus() == null ? 400 : getStatus();
                response.setStatus(status);
                //
                // If we haven't already set a response for the the UI layer, send a stack trace.
                //
                if (out_data == null) {
                    if (failing_exception != null) {
                        response.sendError(status, ExceptionUtils.getFullStackTrace(failing_exception));
                    } else {
                        response.sendError(status, "No underlying exception");
                    }
                } else {
                    //
                    // If we've setup an error message in the response, then we don't need to return an HTTP status error code.
                    // The client (the UI layer) will parse the response and recognize it contains an error response.
                    //
                    if (out_data.contains("isError")) {
                        setSuccessStatus();
                    }
                }
            } else {
                // Success
                setSession();
                if (rpath != null) {
                    // Redirect
                    StringBuffer path = new StringBuffer();
                    for (String part : rpath) {
                        if ("".equals(part))
                            continue;
                        path.append('/');
                        path.append(part);
                    }
                    boolean first = true;
                    for (Map.Entry<String, String> e : rargs.entrySet()) {
                        path.append(first ? '?' : '&');
                        first = false;
                        path.append(URLEncoder.encode(e.getKey(), "UTF-8"));
                        path.append('=');
                        path.append(URLEncoder.encode(e.getValue(), "UTF-8"));
                    }
                    if (secondary_redirect)
                        setSuccessStatus();
                    else
                        response.setStatus(303);
                    response.setHeader("Location", path.toString());
                } else {
                    setSuccessStatus();
                }
            }
            if (close)
                close();
        } catch (IOException e) {
            throw new UIException("Could not send error", e);
        } finally {
            out_data = null; // reset the out_data field for possible additional requests?
        }
    }

    @Override
    public void sendXMLResponse(String data) throws UIException {
        response.setContentType("text/xml;charset=UTF-8");
        out_data = data;
    }

    @Override
    public void sendUnknown(String data, String contenttype, String contentDisposition) throws UIException {
        response.setContentType(contenttype);
        if (Tools.notEmpty(contentDisposition))
            response.setHeader("Content-Disposition", contentDisposition);
        out_data = data;
    }

    @Override
    public void sendUnknown(byte[] data, String contenttype, String contentDisposition) throws UIException {
        response.setContentType(contenttype);
        if (Tools.notEmpty(contentDisposition))
            response.setHeader("Content-Disposition", contentDisposition);
        out_binary_data = data;
    }

    @Override
    public void sendUnknown(InputStream data, String contenttype, String contentDisposition) throws UIException {
        response.setContentType(contenttype);
        if (Tools.notEmpty(contentDisposition))
            response.setHeader("Content-Disposition", contentDisposition);
        out_input_stream = data;
    }

    @Override
    public void sendJSONResponse(JSONObject data) throws UIException {
        response.setContentType("text/json;charset=UTF-8");
        out_data = data.toString();
    }

    @Override
    public void sendJSONResponse(JSONArray data) throws UIException {
        response.setContentType("text/json;charset=UTF-8");
        out_data = data.toString();
    }

    @Override
    public String getFileName() throws UIException {
        return uploadName;
    }

    @Override
    public byte[] getbyteBody() throws UIException {
        return bytebody;
    }

    @Override
    public String getContentType() throws UIException {
        return contenttype;
    }

    @Override
    public String getBody() throws UIException {
        return body;
    }

    @Override
    public JSONObject getPostBody() throws UIException {
        JSONObject jsondata = new JSONObject();
        String jsonString = body;
        try {
            if (jsonString.length() > 0) {
                String[] data = jsonString.split("&");
                for (String item : data) {
                    String[] itembits = item.split("=");
                    jsondata.put(URLDecoder.decode(itembits[0], "UTF-8"), URLDecoder.decode(itembits[1], "UTF-8"));
                }
            }

        } catch (JSONException e) {
            throw new UIException("Cannot get request body, JSONException", e);
        } catch (UnsupportedEncodingException e) {
            throw new UIException("Cannot get request body, UnsupportedEncodingException", e);
        }
        return jsondata;
    }

    @Override
    public Boolean isJSON() throws UIException {
        try {
            new JSONObject(body);
            return true;
        } catch (JSONException e) {
            return false;
        }
    }

    @Override
    public JSONObject getJSONBody() throws UIException {
        try {
            String jsonString = body;
            if (StringUtils.isBlank(jsonString)) {
                throw new UIException("No JSON content to store");
            }

            // Store it
            return new JSONObject(jsonString);
        } catch (JSONException e) {
            throw new UIException("Cannot get request body, JSONException", e);
        }
    }

    @Override
    public UISession getSession() throws UIException {
        return session;
    }

    @Override
    public HttpSession getHttpSession() {
        return request.getSession(true);
    }

    @Override
    public void sendURLReponse(String url) throws UIException {
        response.setHeader("Location", url);
    }

    @Override
    public String getTenant() {
        // TODO Auto-generated method stub
        return this.umbrella.getWebUI().getTenant();
    }

    @Override
    public void setStatus(int status) {
        this.status = status;
    }

    @Override
    public Integer getStatus() {
        return status;
    }
}