001/*
002 * Copyright 2012-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.autoconfigure.hateoas;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.List;
022
023import javax.annotation.PostConstruct;
024
025import org.springframework.beans.BeansException;
026import org.springframework.beans.factory.BeanFactory;
027import org.springframework.beans.factory.BeanFactoryAware;
028import org.springframework.beans.factory.ListableBeanFactory;
029import org.springframework.beans.factory.config.BeanPostProcessor;
030import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
031import org.springframework.context.annotation.Bean;
032import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
033import org.springframework.http.MediaType;
034import org.springframework.http.converter.AbstractHttpMessageConverter;
035import org.springframework.http.converter.HttpMessageConverter;
036import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
037
038/**
039 * Configuration for {@link HttpMessageConverter HttpMessageConverters} when hypermedia is
040 * enabled.
041 *
042 * @author Andy Wilkinson
043 */
044public class HypermediaHttpMessageConverterConfiguration {
045
046        @Bean
047        @ConditionalOnProperty(prefix = "spring.hateoas", name = "use-hal-as-default-json-media-type", matchIfMissing = true)
048        public static HalMessageConverterSupportedMediaTypesCustomizer halMessageConverterSupportedMediaTypeCustomizer() {
049                return new HalMessageConverterSupportedMediaTypesCustomizer();
050        }
051
052        /**
053         * Updates any {@link TypeConstrainedMappingJackson2HttpMessageConverter}s to support
054         * {@code application/json} in addition to {@code application/hal+json}. Cannot be a
055         * {@link BeanPostProcessor} as processing must be performed after
056         * {@code Jackson2ModuleRegisteringBeanPostProcessor} has registered the converter and
057         * it is unordered.
058         */
059        private static class HalMessageConverterSupportedMediaTypesCustomizer
060                        implements BeanFactoryAware {
061
062                private volatile BeanFactory beanFactory;
063
064                @PostConstruct
065                public void configureHttpMessageConverters() {
066                        if (this.beanFactory instanceof ListableBeanFactory) {
067                                configureHttpMessageConverters(((ListableBeanFactory) this.beanFactory)
068                                                .getBeansOfType(RequestMappingHandlerAdapter.class).values());
069                        }
070                }
071
072                private void configureHttpMessageConverters(
073                                Collection<RequestMappingHandlerAdapter> handlerAdapters) {
074                        for (RequestMappingHandlerAdapter handlerAdapter : handlerAdapters) {
075                                for (HttpMessageConverter<?> messageConverter : handlerAdapter
076                                                .getMessageConverters()) {
077                                        configureHttpMessageConverter(messageConverter);
078                                }
079                        }
080                }
081
082                private void configureHttpMessageConverter(HttpMessageConverter<?> converter) {
083                        if (converter instanceof TypeConstrainedMappingJackson2HttpMessageConverter) {
084                                List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(
085                                                converter.getSupportedMediaTypes());
086                                if (!supportedMediaTypes.contains(MediaType.APPLICATION_JSON)) {
087                                        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
088                                }
089                                ((AbstractHttpMessageConverter<?>) converter)
090                                                .setSupportedMediaTypes(supportedMediaTypes);
091                        }
092                }
093
094                @Override
095                public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
096                        this.beanFactory = beanFactory;
097                }
098
099        }
100
101}