Java tutorial
/* * 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.ops.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.ops.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.getProduces(); 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; } } }