001/*
002 * Copyright 2012-2018 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.context.annotation.Configuration;
033import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
034import org.springframework.http.MediaType;
035import org.springframework.http.converter.AbstractHttpMessageConverter;
036import org.springframework.http.converter.HttpMessageConverter;
037import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
038
039/**
040 * Configuration for {@link HttpMessageConverter HttpMessageConverters} when hypermedia is
041 * enabled.
042 *
043 * @author Andy Wilkinson
044 */
045@Configuration
046public class HypermediaHttpMessageConverterConfiguration {
047
048        @Bean
049        @ConditionalOnProperty(prefix = "spring.hateoas", name = "use-hal-as-default-json-media-type", matchIfMissing = true)
050        public static HalMessageConverterSupportedMediaTypesCustomizer halMessageConverterSupportedMediaTypeCustomizer() {
051                return new HalMessageConverterSupportedMediaTypesCustomizer();
052        }
053
054        /**
055         * Updates any {@link TypeConstrainedMappingJackson2HttpMessageConverter}s to support
056         * {@code application/json} in addition to {@code application/hal+json}. Cannot be a
057         * {@link BeanPostProcessor} as processing must be performed after
058         * {@code Jackson2ModuleRegisteringBeanPostProcessor} has registered the converter and
059         * it is unordered.
060         */
061        private static class HalMessageConverterSupportedMediaTypesCustomizer
062                        implements BeanFactoryAware {
063
064                private volatile BeanFactory beanFactory;
065
066                @PostConstruct
067                public void configureHttpMessageConverters() {
068                        if (this.beanFactory instanceof ListableBeanFactory) {
069                                configureHttpMessageConverters(((ListableBeanFactory) this.beanFactory)
070                                                .getBeansOfType(RequestMappingHandlerAdapter.class).values());
071                        }
072                }
073
074                private void configureHttpMessageConverters(
075                                Collection<RequestMappingHandlerAdapter> handlerAdapters) {
076                        for (RequestMappingHandlerAdapter handlerAdapter : handlerAdapters) {
077                                for (HttpMessageConverter<?> messageConverter : handlerAdapter
078                                                .getMessageConverters()) {
079                                        configureHttpMessageConverter(messageConverter);
080                                }
081                        }
082                }
083
084                private void configureHttpMessageConverter(HttpMessageConverter<?> converter) {
085                        if (converter instanceof TypeConstrainedMappingJackson2HttpMessageConverter) {
086                                List<MediaType> supportedMediaTypes = new ArrayList<>(
087                                                converter.getSupportedMediaTypes());
088                                if (!supportedMediaTypes.contains(MediaType.APPLICATION_JSON)) {
089                                        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
090                                }
091                                ((AbstractHttpMessageConverter<?>) converter)
092                                                .setSupportedMediaTypes(supportedMediaTypes);
093                        }
094                }
095
096                @Override
097                public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
098                        this.beanFactory = beanFactory;
099                }
100
101        }
102
103}