ste.web.http.api.ApiHandler.java Source code

Java tutorial

Introduction

Here is the source code for ste.web.http.api.ApiHandler.java

Source

/*
 * BeanShell Web
 * Copyright (C) 2015 Stefano Fornari
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY Stefano Fornari, Stefano Fornari
 * DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * 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 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 or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 */
package ste.web.http.api;

import bsh.EvalError;
import bsh.Interpreter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.RequestLine;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.FileEntity;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import ste.web.http.HttpSessionContext;
import ste.web.http.MimeUtils;
import static ste.web.http.api.Constants.*;
import ste.web.http.beanshell.BeanShellUtils;

/**
 *
 * @author ste
 */
public class ApiHandler implements HttpRequestHandler {

    private final String apiroot, webcontext;
    private final Logger log;

    /**
     * 
     * @param apiroot parent directory of the API tree structure (usually 
     *                the web root) - NOT NULL
     * @param webcontext optional webcontext that identifies a web call; id must
     *                   not contain a tailing slah (e.g. /my/webcontext) - NOT NULL
     * 
     * @throws IllegalArgumentException if webroot is null
     * 
     */
    public ApiHandler(final String apiroot, final String webcontext) {
        if (apiroot == null) {
            throw new IllegalArgumentException("apiroot can not be null");
        }

        this.webcontext = webcontext;
        this.apiroot = apiroot;
        this.log = Logger.getLogger(LOG_NAME);
    }

    /**
     * Shortcut for new ApiHandler(apiroot, "/api").
     * 
     * @param apiroot
     * 
     */
    public ApiHandler(final String apiroot) {
        this(apiroot, "/api");
    }

    /**
     * Note that we expect response to have a body entity set (@see HttpEntiry)
     * 
     * @param request
     * @param response
     * @param context
     * 
     * @throws HttpException
     * @throws IOException 
     */
    @Override
    public void handle(HttpRequest request, HttpResponse response, HttpContext context)
            throws HttpException, IOException {
        RRequest rr = null;
        File actionScript = null, applicationScript = null;

        try {
            rr = new RRequest(reduce(request.getRequestLine()));

            if (log.isLoggable(Level.FINE)) {
                log.fine(String.format("serving %s", rr.getPath()));
            }

            applicationScript = new File(apiroot, getApplicationScript(rr));
            actionScript = new File(apiroot, getActionScript(rr));

            if (log.isLoggable(Level.FINE)) {
                log.fine(String.format("application script path: %s", applicationScript.getAbsolutePath()));
                log.fine(String.format("action script path: %s", actionScript.getAbsolutePath()));
            }

            Interpreter bsh = new Interpreter();
            BeanShellUtils.setup(bsh, request, response, (HttpSessionContext) context);
            bsh.set(VAR_SOURCE, actionScript.getAbsolutePath());
            bsh.set(VAR_RREQUEST, rr);
            if (applicationScript.exists()) {
                bsh.eval(BeanShellUtils.getScript(applicationScript));
            }
            bsh.eval(BeanShellUtils.getScript(actionScript));

            Object body = bsh.get(rr.getHandler());

            AbstractHttpEntity e = (AbstractHttpEntity) response.getEntity();
            if (e.getContentType() == null) {
                e.setContentType("application/json");
            }

            if (body != null) {
                if (body instanceof File) {
                    File f = (File) body;
                    e = new FileEntity(f);
                    response.setEntity(e);
                    String mimeType = MimeUtils.getInstance().getMimeType(f);

                    e.setContentType(
                            MimeUtils.MIME_UNKNOWN.equals(mimeType) ? "application/octet-stream" : mimeType);
                } else {
                    String bodyString = String.valueOf(body);
                    byte[] buf = bodyString.getBytes();
                    ByteArrayInputStream is = new ByteArrayInputStream(buf);
                    BasicHttpEntity basicEntity = (BasicHttpEntity) e;
                    basicEntity.setContent(is);
                    basicEntity.setContentLength(buf.length);
                    if (e.getContentType() == null) {
                        e.setContentType("application/json");
                    }
                }
            }

            BeanShellUtils.cleanup(bsh, request);
            BeanShellUtils.setVariablesAttributes(bsh, context);
        } catch (FileNotFoundException e) {
            response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_NOT_FOUND,
                    "Script " + actionScript + " not found.");
        } catch (EvalError x) {
            String msg = x.getMessage();

            if (log.isLoggable(Level.SEVERE)) {
                log.severe(String.format("error evaluating: %s: %s", actionScript, msg));
                log.throwing(getClass().getName(), "handleError", x);
            }
            //
            // We shall not expose to the client any details of a server error
            //
            throw new HttpException("server erorr processing the resource - see server log for details", x);
        } catch (URISyntaxException x) {
            response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_REQUEST,
                    StringEscapeUtils.escapeHtml4(x.getMessage()));
        } catch (Exception x) {
            response.setStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_INTERNAL_SERVER_ERROR,
                    StringEscapeUtils.escapeHtml4(x.getMessage()));
        }
    }

    // --------------------------------------------------------- private methods

    private String getApplicationScript(RRequest rr) {
        return rr.getApplication() + '/' + rr.getHandler() + '/' + rr.getApplication() + ".bsh";
    }

    private String getActionScript(RRequest rr) {
        return rr.getApplication() + '/' + rr.getHandler() + '/' + rr.getAction() + ".bsh";
    }

    private String reduce(RequestLine rl) {
        if (StringUtils.isBlank(webcontext)) {
            return rl.getUri();
        }

        return rl.getUri().substring(webcontext.length());
    }

}