All Downloads are FREE. Search and download functionalities are using the official Maven repository.

restx.StdRestxMainRouter Maven / Gradle / Ivy

There is a newer version: 1.2.0-rc2
Show newest version
package restx;

import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import restx.common.metrics.api.MetricRegistry;
import restx.common.metrics.api.Monitor;
import restx.common.metrics.dummy.DummyMetricRegistry;
import restx.exceptions.RestxError;
import restx.exceptions.WrappedCheckedException;
import restx.factory.Factory;
import restx.factory.NamedComponent;
import restx.http.HttpStatus;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * User: xavierhanin
 * Date: 2/6/13
 * Time: 9:53 PM
 */
public class StdRestxMainRouter implements RestxMainRouter {

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private MetricRegistry metrics;
        private String mode = RestxContext.Modes.PROD;
        private int priority = 0;
        private List> filters = Lists.newArrayList();
        private List> routeFilters = Lists.newArrayList();
        private List routes = Lists.newArrayList();

        public Builder withMetrics(MetricRegistry metrics) {
            this.metrics = metrics;
            return this;
        }

        public Builder inMode(String mode) {
            this.mode = mode;
            return this;
        }

        public Builder addFilter(RestxFilter filter) {
            int filterPriority = priority++;
            filters.add(NamedComponent.of(RestxFilter.class, "Filter" + filterPriority, filterPriority, filter));
            return this;
        }

        public Builder addFilter(RestxRouteFilter filter) {
            int filterPriority = priority++;
            routeFilters.add(NamedComponent.of(
                    RestxRouteFilter.class, "RouteFilter" + filterPriority, filterPriority, filter));
            return this;
        }

        public Builder addRouter(RestxRouter router) {
            routes.addAll(router.getRoutes());
            return this;
        }

        public Builder addRoute(RestxRoute route) {
            routes.add(route);
            return this;
        }

        public RestxMainRouter build() {
            return new StdRestxMainRouter(
                    metrics == null ? getMetricsRegistryComponent() : metrics,
                    new RestxRouting(
                            ImmutableList.copyOf(filters),
                            ImmutableList.copyOf(routeFilters),
                            ImmutableList.copyOf(routes)),
                    mode);
        }
    }

    private static MetricRegistry getMetricsRegistryComponent() {
        return Factory.getInstance().getComponent(MetricRegistry.class);
    }

    private static final Logger logger = LoggerFactory.getLogger(StdRestxMainRouter.class);

    private final RestxRouting routing;
    private final String mode;
    private final MetricRegistry metrics;

    public StdRestxMainRouter(RestxRouting routing) {
        this(routing, RestxContext.Modes.PROD);
    }

    public StdRestxMainRouter(RestxRouting routing, String mode) {
        this(getMetricsRegistryComponent(), routing, mode);
    }

    public StdRestxMainRouter(MetricRegistry metrics, RestxRouting routing, String mode) {
        this.metrics = checkNotNull(metrics);
        this.routing = checkNotNull(routing);
        this.mode = checkNotNull(mode);
    }

    @Override
    public void route(RestxRequest restxRequest, final RestxResponse restxResponse) throws IOException {
        logger.debug("<< {}", restxRequest);
        Stopwatch stopwatch = Stopwatch.createStarted();

        Monitor monitor = metrics.timer(" " + restxRequest.getHttpMethod() + " " + restxRequest.getRestxPath()).time();
        try {
            Optional m = routing.match(restxRequest);

            if (!m.isPresent()) {
                // no route matched
                String path = restxRequest.getRestxPath();
                StringBuilder sb = new StringBuilder()
                        .append("no restx route found for ")
                        .append(restxRequest.getHttpMethod()).append(" ").append(path).append("\n");
                if (hasApiDocs()) {
                    sb.append("go to ").append(restxRequest.getBaseUri()).append("/@/ui/api-docs/")
                            .append(" for API documentation\n\n");
                }
                sb.append("routes:\n")
                        .append("-----------------------------------\n");
                for (RestxRoute route : routing.getRoutes()) {
                    sb.append(route).append("\n");
                }
                sb.append("-----------------------------------");
                restxResponse.setStatus(HttpStatus.NOT_FOUND);
                restxResponse.setContentType("text/plain");
                PrintWriter out = restxResponse.getWriter();
                out.print(sb.toString());
            } else {
                MDC.put("restx.path", restxRequest.getRestxPath());
                MDC.put("restx.method", restxRequest.getHttpMethod());

                logger.debug("<< {}\nHANDLERS: {}", restxRequest, m.get().getMatches());
                RestxContext context = new RestxContext(getMode(), new AbstractRouteLifecycleListener() {},
                        ImmutableList.copyOf(m.get().getMatches()));
                RestxHandlerMatch match = context.nextHandlerMatch();
                match.handle(restxRequest, restxResponse, context);
            }
        } catch (JsonProcessingException ex) {
            logger.warn("request raised " + ex.getClass().getSimpleName(), ex);
            restxResponse.setStatus(HttpStatus.BAD_REQUEST);
            restxResponse.setContentType("text/plain");
            PrintWriter out = restxResponse.getWriter();
            if (restxRequest.getContentStream() instanceof BufferedInputStream) {
                try {
                    JsonLocation location = ex.getLocation();
                    restxRequest.getContentStream().reset();
                    out.println(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, ex.getClass().getSimpleName()) + "." +
                            " Please verify your input:");
                    List lines = CharStreams.readLines(
                            new InputStreamReader(restxRequest.getContentStream()));

                    if (lines.isEmpty()) {
                        if ("application/x-www-form-urlencoded".equalsIgnoreCase(restxRequest.getContentType())) {
                            out.println("Body was considered as parameter due to Content-Type: " +
                                    restxRequest.getContentType() + ". " +
                                    "Setting your Content-Type to \"application/json\" may resolve the problem");
                        } else {
                            out.println("Empty body. Content-type was \"" + restxRequest.getContentType() + "\"");
                        }
                    } else {
                        out.println("<-- JSON -->");
                        for (int i = 0; i < lines.size(); i++) {
                            String line = lines.get(i);
                            out.println(line);
                            if (location != null && (i + 1 == location.getLineNr())) {
                                boolean farColumn = location.getColumnNr() > 80;
                                /*
                                 * if error column is too far, we precede the error message with >> to show
                                 * that there is an error message on the line
                                 */
                                out.println(
                                        Strings.repeat(" ", Math.max(0, location.getColumnNr() - 2)) + "^");
                                out.println(Strings.repeat(farColumn ? ">" : " ", Math.max(0, location.getColumnNr()
                                        - (ex.getOriginalMessage().length() / 2) - 3))
                                        + ">> " + ex.getOriginalMessage() + " <<");
                                out.println();
                            }
                        }
                        out.println("");
                    }

                    restxRequest.getContentStream().reset();
                    logger.debug(ex.getClass().getSimpleName() + " on " + restxRequest + "." +
                            " message: " + ex.getMessage() + "." +
                            " request content: " + CharStreams.toString(new InputStreamReader(restxRequest.getContentStream())));
                } catch (IOException e) {
                    logger.warn("io exception raised when trying to provide original input to caller", e);
                    out.println(ex.getMessage());
                }
            }
        } catch (RestxError.RestxException ex) {
            logger.debug("request raised RestxException", ex);
            restxResponse.setStatus(ex.getErrorStatus());
            restxResponse.setContentType("application/json");
            PrintWriter out = restxResponse.getWriter();
            out.println(ex.toJSON());
        } catch (WebException ex) {
            ex.writeTo(restxRequest, restxResponse);
        } catch (IllegalArgumentException | IllegalStateException ex) {
            logger.warn("request raised " + ex.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
            restxResponse.setStatus(HttpStatus.BAD_REQUEST);
            restxResponse.setContentType("text/plain");
            PrintWriter out = restxResponse.getWriter();
            out.println("UNEXPECTED CLIENT ERROR:");
            out.print(ex.getMessage());
        } catch (WrappedCheckedException wrappedException) {
            Throwable ex = wrappedException.getCause();
            logger.error("request raised " + ex.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
            restxResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
            restxResponse.setContentType("text/plain");
            PrintWriter out = restxResponse.getWriter();
            out.println("UNEXPECTED SERVER ERROR:");
            out.print(ex.getMessage());
        } catch (Throwable ex) {
            logger.error("request raised " + ex.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
            restxResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
            restxResponse.setContentType("text/plain");
            PrintWriter out = restxResponse.getWriter();
            out.println("UNEXPECTED SERVER ERROR:");
            out.print(ex.getMessage());
        } finally {
            try { restxRequest.closeContentStream(); } catch (Exception ex) { }
            try { restxResponse.close(); } catch (Exception ex) { }
            monitor.stop();
            stopwatch.stop();
            restxResponse.getLogLevel().log(logger, restxRequest, restxResponse, stopwatch);
            MDC.clear();
        }
    }

    String getMode() {
        return mode;
    }

    private boolean hasApiDocs() {
        for (RestxRoute route : routing.getRoutes()) {
            // maybe we should find a more pluggable way to detect this feature..
            // we don't use the class itself, we don't want to have a strong dependency on swagger route
            if (route.getClass().getName().endsWith("ApiDocsUIRoute")) {
                return true;
            }
        }

        return false;
    }

    public int getNbFilters() {
        return routing.getFilters().size();
    }

    public int getNbRoutes() {
        return routing.getRoutes().size();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy