org.restexpress.pipeline.DefaultRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.restexpress.pipeline.DefaultRequestHandler.java

Source

/*
 * Copyright 2009, Strategic Gains, Inc.
 *
 * 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.restexpress.pipeline;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AttributeKey;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.restexpress.ContentType;
import org.restexpress.Request;
import org.restexpress.Response;
import org.restexpress.exception.DefaultExceptionMapper;
import org.restexpress.exception.ExceptionMapping;
import org.restexpress.exception.ExceptionUtils;
import org.restexpress.exception.ServiceException;
import org.restexpress.response.HttpResponseWriter;
import org.restexpress.route.Action;
import org.restexpress.route.RouteResolver;
import org.restexpress.serialization.SerializationProvider;
import org.restexpress.serialization.SerializationSettings;
import org.restexpress.util.HttpSpecification;

/**
 * @author toddf
 * @since Nov 13, 2009
 */
@Sharable
public class DefaultRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    //SECTION: CONSTANTS
    private static final AttributeKey<MessageContext> CONTEXT_KEY = AttributeKey.valueOf("context");

    // SECTION: INSTANCE VARIABLES

    private RouteResolver routeResolver;
    private SerializationProvider serializationProvider;
    private HttpResponseWriter responseWriter;
    private List<Preprocessor> preprocessors = new ArrayList<Preprocessor>();
    private List<Postprocessor> postprocessors = new ArrayList<Postprocessor>();
    private List<Postprocessor> finallyProcessors = new ArrayList<Postprocessor>();
    private ExceptionMapping exceptionMap = new DefaultExceptionMapper();
    private List<MessageObserver> messageObservers = new ArrayList<MessageObserver>();
    private boolean shouldEnforceHttpSpec = true;

    // SECTION: CONSTRUCTORS

    public DefaultRequestHandler(RouteResolver routeResolver, SerializationProvider serializationProvider,
            HttpResponseWriter responseWriter, boolean enforceHttpSpec) {
        super();
        this.routeResolver = routeResolver;
        this.serializationProvider = serializationProvider;
        setResponseWriter(responseWriter);
        this.shouldEnforceHttpSpec = enforceHttpSpec;
    }

    // SECTION: MUTATORS

    public void addMessageObserver(MessageObserver... observers) {
        for (MessageObserver observer : observers) {
            if (!messageObservers.contains(observer)) {
                messageObservers.add(observer);
            }
        }
    }

    public <T extends Throwable, U extends ServiceException> DefaultRequestHandler mapException(Class<T> from,
            Class<U> to) {
        exceptionMap.map(from, to);
        return this;
    }

    public DefaultRequestHandler setExceptionMap(ExceptionMapping map) {
        this.exceptionMap = map;
        return this;
    }

    public HttpResponseWriter getResponseWriter() {
        return this.responseWriter;
    }

    public void setResponseWriter(HttpResponseWriter writer) {
        this.responseWriter = writer;
    }

    // SECTION: SIMPLE-CHANNEL-UPSTREAM-HANDLER

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest event) throws Exception {
        MessageContext context = createInitialContext(ctx, event);

        try {
            // Process the request
            processRequest(ctx, context);
        } catch (Throwable t) {
            handleRestExpressException(ctx, t);
        } finally {
            notifyComplete(context);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        super.channelReadComplete(ctx);
    }

    private void processRequest(ChannelHandlerContext ctx, MessageContext context) throws Throwable {
        notifyReceived(context);
        resolveRoute(context);
        resolveResponseProcessor(context);
        invokePreprocessors(preprocessors, context.getRequest());
        Object result = context.getAction().invoke(context.getRequest(), context.getResponse());

        if (result != null) {
            context.getResponse().setBody(result);
        }

        invokePostprocessors(postprocessors, context.getRequest(), context.getResponse());
        serializeResponse(context, false);
        enforceHttpSpecification(context);
        invokeFinallyProcessors(finallyProcessors, context.getRequest(), context.getResponse());
        writeResponse(ctx, context);
        notifySuccess(context);
    }

    private void resolveResponseProcessor(MessageContext context) {
        SerializationSettings s = serializationProvider.resolveResponse(context.getRequest(), context.getResponse(),
                false);
        context.setSerializationSettings(s);
    }

    /**
      * @param context
      */
    private void enforceHttpSpecification(MessageContext context) {
        if (shouldEnforceHttpSpec) {
            HttpSpecification.enforce(context.getResponse());
        }
    }

    private void handleRestExpressException(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        MessageContext context = (MessageContext) ctx.attr(CONTEXT_KEY).get();
        Throwable rootCause = mapServiceException(cause);

        if (rootCause != null) // was/is a ServiceException
        {
            context.setHttpStatus(((ServiceException) rootCause).getHttpStatus());

            if (ServiceException.class.isAssignableFrom(rootCause.getClass())) {
                ((ServiceException) rootCause).augmentResponse(context.getResponse());
            }
        } else {
            rootCause = ExceptionUtils.findRootCause(cause);
            context.setHttpStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
        }

        context.setException(rootCause);
        notifyException(context);
        serializeResponse(context, true);
        invokeFinallyProcessors(finallyProcessors, context.getRequest(), context.getResponse());
        writeResponse(ctx, context);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) throws Exception {
        try {
            MessageContext messageContext = (MessageContext) ctx.attr(CONTEXT_KEY).get();

            if (messageContext != null) {
                messageContext.setException(throwable.getCause() != null ? throwable.getCause() : throwable);
                notifyException(messageContext);
            }
        } catch (Throwable t) {
            System.err.print("DefaultRequestHandler.exceptionCaught() threw an exception.");
            t.printStackTrace();
        } finally {
            ctx.channel().close();
        }
    }

    private MessageContext createInitialContext(ChannelHandlerContext ctx, FullHttpRequest httpRequest) {
        Request request = createRequest(httpRequest, ctx);
        Response response = createResponse();
        MessageContext context = new MessageContext(request, response);
        ctx.attr(CONTEXT_KEY).set(context);
        return context;
    }

    private void resolveRoute(MessageContext context) {
        Action action = routeResolver.resolve(context.getRequest());
        context.setAction(action);
    }

    private void notifyReceived(MessageContext context) {
        for (MessageObserver observer : messageObservers) {
            observer.onReceived(context.getRequest(), context.getResponse());
        }
    }

    private void notifyComplete(MessageContext context) {
        for (MessageObserver observer : messageObservers) {
            observer.onComplete(context.getRequest(), context.getResponse());
        }
    }

    // SECTION: UTILITY -- PRIVATE

    private void notifyException(MessageContext context) {
        Throwable exception = context.getException();

        for (MessageObserver observer : messageObservers) {
            observer.onException(exception, context.getRequest(), context.getResponse());
        }
    }

    private void notifySuccess(MessageContext context) {
        for (MessageObserver observer : messageObservers) {
            observer.onSuccess(context.getRequest(), context.getResponse());
        }
    }

    public void addPreprocessor(Preprocessor handler) {
        if (!preprocessors.contains(handler)) {
            preprocessors.add(handler);
        }
    }

    public void addPostprocessor(Postprocessor handler) {
        if (!postprocessors.contains(handler)) {
            postprocessors.add(handler);
        }
    }

    public void addFinallyProcessor(Postprocessor handler) {
        if (!finallyProcessors.contains(handler)) {
            finallyProcessors.add(handler);
        }
    }

    private void invokePreprocessors(List<Preprocessor> processors, Request request) {
        for (Preprocessor handler : processors) {
            handler.process(request);
        }

        request.getBody().resetReaderIndex();
    }

    private void invokePostprocessors(List<Postprocessor> processors, Request request, Response response) {
        for (Postprocessor handler : processors) {
            handler.process(request, response);
        }
    }

    private void invokeFinallyProcessors(List<Postprocessor> processors, Request request, Response response) {
        for (Postprocessor handler : processors) {
            try {
                handler.process(request, response);
            } catch (Throwable t) {
                t.printStackTrace(System.err);
            }
        }
    }

    /**
     * Uses the exceptionMap to map a Throwable to a ServiceException, if possible.
     *
     * @param cause
     * @return Either a ServiceException or the root cause of the exception.
     */
    private Throwable mapServiceException(Throwable cause) {
        if (ServiceException.isAssignableFrom(cause)) {
            return cause;
        }

        return exceptionMap.getExceptionFor(cause);
    }

    private Request createRequest(FullHttpRequest request, ChannelHandlerContext context) {
        try {
            return new Request((InetSocketAddress) context.channel().remoteAddress(), request, routeResolver,
                    serializationProvider);
        } catch (Throwable t) {
            return new Request(request, routeResolver, serializationProvider);
        }
    }

    private Response createResponse() {
        return new Response();
    }

    private void writeResponse(ChannelHandlerContext ctx, MessageContext context) {
        getResponseWriter().write(ctx, context.getRequest(), context.getResponse());
    }

    private void serializeResponse(MessageContext context, boolean force) {
        Response response = context.getResponse();

        if (HttpSpecification.isContentTypeAllowed(response)) {
            SerializationSettings settings = null;

            if (response.hasSerializationSettings()) {
                settings = response.getSerializationSettings();
            } else if (force) {
                settings = serializationProvider.resolveResponse(context.getRequest(), response, force);
            }

            if (settings != null) {
                if (response.isSerialized()) {
                    ByteBuffer serialized = settings.serialize(response);

                    if (serialized != null) {
                        response.setBody(Unpooled.wrappedBuffer(serialized));

                        if (!response.hasHeader(HttpHeaders.Names.CONTENT_TYPE)) {
                            response.setContentType(settings.getMediaType());
                        }
                    }
                }
            }

            if (!response.hasHeader(HttpHeaders.Names.CONTENT_TYPE)) {
                response.setContentType(ContentType.TEXT_PLAIN);
            }
        }
    }
}