org.aludratest.cloud.impl.request.ClientRequestServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.aludratest.cloud.impl.request.ClientRequestServlet.java

Source

/*
 * Copyright (C) 2010-2015 AludraTest.org and the contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.aludratest.cloud.impl.request;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aludratest.cloud.app.CloudManagerApp;
import org.aludratest.cloud.impl.user.BasicAuthUtil;
import org.aludratest.cloud.user.User;
import org.apache.commons.io.IOUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A servlet which is able to handle REST-like resource requests. Resource requests must fulfill these requirements:
 * <ul>
 * <li>Content Type must be <code>application/json</code>, encoding must be <code>UTF-8</code></li>
 * <li>HTTP method must be <code>POST</code></li>
 * <li>A Basic Authentication header must be sent (<code>Authorization: Basic; ...</code>), specifying a valid user / password
 * combination</li>
 * <li>Content must be a JSON object which is accepted by {@link ClientRequestHandler#handleResourceRequest(User, JSONObject)}</li>
 * </ul>
 * 
 * The servlet will answer with the JSON object received from the {@link ClientRequestHandler}, possibly indicating that no
 * resource has become available within an internal given timeout (usually 10 seconds), and the client should send a new HTTP
 * request to retrieve the status of the pending resource request. See {@link ClientRequestHandler} for more details on the JSON
 * object formats.
 * 
 * @author falbrech
 * 
 */
public class ClientRequestServlet extends HttpServlet {

    private static final long serialVersionUID = 4695231001799464144L;

    private static final int MAX_CONTENT_LENGTH = 1024 * 2; // 2 KB requests only, please

    private static final Logger LOG = LoggerFactory.getLogger(ClientRequestServlet.class);

    // can be used for debugging with curl
    // do not use final to avoid compiler warnings
    private static boolean CONTENT_TYPE_CHECK_ENABLED = true;

    private static final String JSON_CONTENT_TYPE = "application/json";

    private static final Pattern PATTERN_RESOURCE_ID_URI = Pattern.compile("/([0-9a-fA-F]{16})");

    private ClientRequestHandler requestHandler;

    private GateKeeper requestSeparator = new GateKeeper(100, TimeUnit.MILLISECONDS);

    // for debugging purposes
    static ClientRequestServlet instance;

    AtomicInteger waitingRequests = new AtomicInteger();

    @Override
    public void init(ServletConfig config) throws ServletException {
        instance = this;
        super.init(config);

        requestHandler = new ClientRequestHandler(CloudManagerApp.getInstance().getResourceManager());
        // register request handler as MBean
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        try {
            mbs.registerMBean(requestHandler, new ObjectName("org.aludratest.cloud:type=ClientRequestHandler"));
        } catch (JMException e) {
            LOG.error("Could not register request handler in MBean Server", e);
        }
    }

    // for debugging purposes
    ClientRequestHandler getRequestHandler() {
        return requestHandler;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // must be /resource
        String uri = req.getServletPath();
        if (!"/resource".equals(uri)) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // must be /<ID>
        uri = req.getPathInfo();
        Matcher m = PATTERN_RESOURCE_ID_URI.matcher(uri);
        if (!m.matches()) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String resourceKey = m.group(1);

        if (requestHandler.handleReleaseRequest(resourceKey)) {
            resp.setStatus(HttpServletResponse.SC_OK);
        } else {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            requestSeparator.enter();
        } catch (InterruptedException e) {
            return;
        }
        LOG.debug("doPost() enter");

        try {
            // small protection against DoS attacks
            if (req.getContentLength() > MAX_CONTENT_LENGTH) {
                LOG.debug("Detected request larger than max content length. Sending BAD_REQUEST.");
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }

            // must be /resource
            // TODO should be removed; is subject to Application container to register Servlet whereever needed
            String uri = req.getServletPath();
            if (!"/resource".equals(uri)) {
                LOG.debug("Detected request to other path than /resource. Sending NOT_FOUND.");
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }

            User user = BasicAuthUtil.authenticate(req, resp);
            if (user == null) {
                LOG.debug("No or invalid user information in request. Aborting.");
                return;
            }

            // request must be JSON
            String contentType = req.getContentType();
            if (contentType != null && contentType.contains(";")) {
                contentType = contentType.substring(0, contentType.indexOf(';'));
            }
            if (CONTENT_TYPE_CHECK_ENABLED && !JSON_CONTENT_TYPE.equals(contentType)) {
                LOG.debug("Invalid content type detected. Sending BAD_REQUEST.");
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }

            // encoding must be UTF-8
            if (req.getCharacterEncoding() != null && !"UTF-8".equalsIgnoreCase(req.getCharacterEncoding())) {
                LOG.debug("Invalid character encoding detected. Sending BAD_REQUEST.");
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }

            // extract JSON payload
            InputStream data = req.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(data, baos);
            data.close();

            String jsonSource = new String(baos.toByteArray(), "UTF-8");
            try {
                JSONObject requestObject = new JSONObject(jsonSource);
                waitingRequests.incrementAndGet();
                JSONObject resultObject = requestHandler.handleResourceRequest(user, requestObject);
                waitingRequests.decrementAndGet();

                // send it to response
                StringWriter writer = new StringWriter();
                resultObject.write(writer);
                resp.setStatus(HttpServletResponse.SC_OK);
                resp.setCharacterEncoding("UTF-8");
                resp.setContentType(JSON_CONTENT_TYPE);
                byte[] resultData = writer.toString().getBytes("UTF-8");
                resp.setContentLength(resultData.length);

                try {
                    OutputStream os = resp.getOutputStream();
                    os.write(resultData);
                    os.close();
                } catch (IOException e) {
                    // client closed connection during wait
                    if (resultObject.has("requestId")) {
                        requestHandler.abortWaitingRequest(resultObject.getString("requestId"));
                    }
                }
            } catch (JSONException e) {
                LOG.debug("JSON exception occurred. Sending BAD_REQUEST.");
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
                return;
            }
        } finally {
            LOG.debug("doPost() leave");
        }
    }

}