org.wso2.carbon.mss.internal.router.HttpMethodInfo.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.mss.internal.router.HttpMethodInfo.java

Source

/*
 *  Copyright (c) WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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.wso2.carbon.mss.internal.router;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMultimap;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import org.wso2.carbon.mss.HttpHandler;
import org.wso2.carbon.mss.HttpResponder;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * HttpMethodInfo is a helper class having state information about the http handler method to be invoked, the handler
 * and arguments required for invocation by the Dispatcher. RequestRouter populates this class and stores in its
 * context as attachment.
 */
class HttpMethodInfo {

    private final Method method;
    private final HttpHandler handler;
    private final boolean isChunkedRequest;
    private final ByteBuf requestContent;
    private final HttpRequest request;
    private final HttpResponder responder;
    private final Object[] args;
    private final boolean isStreaming;
    private final ExceptionHandler exceptionHandler;

    private BodyConsumer bodyConsumer;

    HttpMethodInfo(Method method, HttpHandler handler, HttpRequest request, HttpResponder responder, Object[] args,
            ExceptionHandler exceptionHandler) {
        this.method = method;
        this.handler = handler;
        this.isChunkedRequest = request instanceof HttpContent;
        if (request instanceof HttpContent) {
            requestContent = ((HttpContent) request).content();
        } else {
            this.requestContent = null;
        }
        this.isStreaming = BodyConsumer.class.isAssignableFrom(method.getReturnType());
        this.request = rewriteRequest(request, isStreaming);
        this.responder = responder;
        this.exceptionHandler = exceptionHandler;

        // The actual arguments list to invoke handler method
        this.args = new Object[args.length + 2];
        this.args[0] = request;
        this.args[1] = responder;
        System.arraycopy(args, 0, this.args, 2, args.length);
    }

    /**
     * Calls the httpHandler method.
     */
    void invoke() throws Exception {
        if (isStreaming) {
            // Casting guarantee to be succeeded.
            bodyConsumer = (BodyConsumer) method.invoke(handler, args);
            if (bodyConsumer != null) {
                if (requestContent.isReadable()) {
                    bodyConsumerChunk(requestContent);
                }
                if (!isChunkedRequest) {
                    bodyConsumerFinish();
                }
            }
        } else {
            // Actually <T> would be void
            bodyConsumer = null;
            try {
                method.invoke(handler, args);
            } catch (InvocationTargetException e) {
                exceptionHandler.handle(e.getTargetException(), request, responder);
            }
        }
    }

    void chunk(HttpContent chunk) throws Exception {
        if (bodyConsumer == null) {
            // If the handler method doesn't want to handle chunk request, the bodyConsumer will be null.
            // It applies to case when the handler method inspects the request and decides to decline it.
            // Usually the handler also closes the connection after declining the request.
            // However, depending on the closing time and the request,
            // there may be some chunk of data already sent by the client.
            return;
        }
        if (chunk instanceof LastHttpContent) { //TODO: azeez
            bodyConsumerFinish();
        } else {
            bodyConsumerChunk(chunk.content());
        }
    }

    /**
     * Calls the {@link BodyConsumer#chunk(ByteBuf, HttpResponder)} method. If the chunk method call
     * throws exception, the {@link BodyConsumer#handleError(Throwable)} will be called and this method will
     * throw {@link HandlerException}.
     */
    private void bodyConsumerChunk(ByteBuf buffer) throws HandlerException {
        try {
            bodyConsumer.chunk(buffer, responder);
        } catch (Throwable t) {
            throw bodyConsumerError(t);
        }
    }

    /**
     * Calls {@link BodyConsumer#finished(HttpResponder)} method. The current bodyConsumer will be set to {@code null}
     * after the call.
     */
    private void bodyConsumerFinish() {
        BodyConsumer consumer = bodyConsumer;
        bodyConsumer = null;
        consumer.finished(responder);
    }

    /**
     * Calls {@link BodyConsumer#handleError(Throwable)} and throws {@link HandlerException}. The current
     * bodyConsumer will be set to {@code null} after the call.
     */
    private HandlerException bodyConsumerError(Throwable cause) throws HandlerException {
        BodyConsumer consumer = bodyConsumer;
        bodyConsumer = null;
        consumer.handleError(cause);

        throw new HandlerException(HttpResponseStatus.INTERNAL_SERVER_ERROR, "", cause);
    }

    /**
     * Sends the error to responder.
     */
    void sendError(HttpResponseStatus status, Throwable ex) {
        String msg;

        if (ex instanceof InvocationTargetException) {
            msg = String.format("Exception Encountered while processing request : %s",
                    Objects.firstNonNull(ex.getCause(), ex).getMessage());
        } else {
            msg = String.format("Exception Encountered while processing request: %s", ex.getMessage());
        }

        // Send the status and message, followed by closing of the connection.
        responder.sendString(status, msg,
                ImmutableMultimap.of(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE));
    }

    /**
     * Returns true if the handler method's return type is BodyConsumer.
     */
    boolean isStreaming() {
        return isStreaming;
    }

    private HttpRequest rewriteRequest(HttpRequest request, boolean isStreaming) {
        if (!isStreaming) {
            return request;
        }

        //TODO: Azeez handle chunks
        /*boolean isChunked = request.headers().contains(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED, true)
        if (!isChunked || request.content().readable()) {
          request.setChunked(true);
          request.setContent(Unpooled.EMPTY_BUFFER);
        }*/
        return request;
    }
}