org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerAdapter.java

Source

/*
 * Copyright 2012-2013 the original author or authors.
 *
 * 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.springframework.boot.actuate.endpoint.mvc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor;

import com.fasterxml.jackson.databind.SerializationFeature;

/**
 * MVC {@link HandlerAdapter} for {@link Endpoint}s. Similar in may respects to
 * {@link AbstractMessageConverterMethodProcessor} but not tied to annotated methods.
 * 
 * @author Phillip Webb
 * @see EndpointHandlerMapping
 */
public final class EndpointHandlerAdapter implements HandlerAdapter {

    private final Log logger = LogFactory.getLog(getClass());

    private static final MediaType MEDIA_TYPE_APPLICATION = new MediaType("application");

    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    private List<HttpMessageConverter<?>> messageConverters;

    private List<MediaType> allSupportedMediaTypes;

    public EndpointHandlerAdapter() {
        WebMvcConfigurationSupportConventions conventions = new WebMvcConfigurationSupportConventions();
        setMessageConverters(conventions.getDefaultHttpMessageConverters());
    }

    @Override
    public boolean supports(Object handler) {
        return handler instanceof Endpoint;
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        return -1;
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        handle(request, response, (Endpoint<?>) handler);
        return null;
    }

    @SuppressWarnings("unchecked")
    private void handle(HttpServletRequest request, HttpServletResponse response, Endpoint<?> endpoint)
            throws Exception {

        Object result = endpoint.invoke();
        Class<?> resultClass = result.getClass();

        List<MediaType> mediaTypes = getMediaTypes(request, endpoint, resultClass);
        MediaType selectedMediaType = selectMediaType(mediaTypes);

        ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
        try {
            if (selectedMediaType != null) {
                selectedMediaType = selectedMediaType.removeQualityValue();
                for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                    if (messageConverter.canWrite(resultClass, selectedMediaType)) {
                        ((HttpMessageConverter<Object>) messageConverter).write(result, selectedMediaType,
                                outputMessage);
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Written [" + result + "] as \"" + selectedMediaType + "\" using ["
                                    + messageConverter + "]");
                        }
                        return;
                    }
                }
            }
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        } finally {
            outputMessage.close();
        }
    }

    private List<MediaType> getMediaTypes(HttpServletRequest request, Endpoint<?> endpoint, Class<?> resultClass)
            throws HttpMediaTypeNotAcceptableException {
        List<MediaType> requested = getAcceptableMediaTypes(request);
        List<MediaType> producible = getProducibleMediaTypes(endpoint, resultClass);

        Set<MediaType> compatible = new LinkedHashSet<MediaType>();
        for (MediaType r : requested) {
            for (MediaType p : producible) {
                if (r.isCompatibleWith(p)) {
                    compatible.add(getMostSpecificMediaType(r, p));
                }
            }
        }
        if (compatible.isEmpty()) {
            throw new HttpMediaTypeNotAcceptableException(producible);
        }
        List<MediaType> mediaTypes = new ArrayList<MediaType>(compatible);
        MediaType.sortBySpecificityAndQuality(mediaTypes);
        return mediaTypes;
    }

    private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
            throws HttpMediaTypeNotAcceptableException {
        List<MediaType> mediaTypes = this.contentNegotiationManager
                .resolveMediaTypes(new ServletWebRequest(request));
        return mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes;
    }

    private List<MediaType> getProducibleMediaTypes(Endpoint<?> endpoint, Class<?> returnValueClass) {
        MediaType[] mediaTypes = endpoint.produces();
        if (mediaTypes != null && mediaTypes.length != 0) {
            return Arrays.asList(mediaTypes);
        }

        if (this.allSupportedMediaTypes.isEmpty()) {
            return Collections.singletonList(MediaType.ALL);
        }

        List<MediaType> result = new ArrayList<MediaType>();
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            if (converter.canWrite(returnValueClass, null)) {
                result.addAll(converter.getSupportedMediaTypes());
            }
        }
        return result;
    }

    private MediaType getMostSpecificMediaType(MediaType acceptType, MediaType produceType) {
        produceType = produceType.copyQualityValue(acceptType);
        return MediaType.SPECIFICITY_COMPARATOR.compare(acceptType, produceType) <= 0 ? acceptType : produceType;
    }

    private MediaType selectMediaType(List<MediaType> mediaTypes) {
        MediaType selectedMediaType = null;
        for (MediaType mediaType : mediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            } else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
        return selectedMediaType;
    }

    public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
        this.contentNegotiationManager = contentNegotiationManager;
    }

    public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        this.messageConverters = messageConverters;
        Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
        for (HttpMessageConverter<?> messageConverter : messageConverters) {
            allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
        }
        this.allSupportedMediaTypes = new ArrayList<MediaType>(allSupportedMediaTypes);
        MediaType.sortBySpecificity(this.allSupportedMediaTypes);
    }

    /**
     * Default conventions, taken from {@link WebMvcConfigurationSupport} with a few minor
     * tweaks.
     */
    private static class WebMvcConfigurationSupportConventions extends WebMvcConfigurationSupport {
        public List<HttpMessageConverter<?>> getDefaultHttpMessageConverters() {
            List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
            addDefaultHttpMessageConverters(converters);
            for (HttpMessageConverter<?> converter : converters) {
                if (converter instanceof MappingJackson2HttpMessageConverter) {
                    MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
                    jacksonConverter.getObjectMapper().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
                }
            }
            return converters;
        }
    }
}