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.actuate.web.mappings.servlet;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.stream.Collectors;
027import java.util.stream.Stream;
028
029import javax.servlet.Servlet;
030
031import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription;
032import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider;
033import org.springframework.boot.web.servlet.ServletRegistrationBean;
034import org.springframework.context.ApplicationContext;
035import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
036import org.springframework.util.ClassUtils;
037import org.springframework.web.context.WebApplicationContext;
038import org.springframework.web.method.HandlerMethod;
039import org.springframework.web.servlet.DispatcherServlet;
040import org.springframework.web.servlet.HandlerMapping;
041import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping;
042import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
043import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
044
045/**
046 * A {@link MappingDescriptionProvider} that introspects the {@link HandlerMapping
047 * HandlerMappings} that are known to one or more {@link DispatcherServlet
048 * DispatcherServlets}.
049 *
050 * @author Andy Wilkinson
051 * @author Stephane Nicoll
052 * @since 2.0.0
053 */
054public class DispatcherServletsMappingDescriptionProvider
055                implements MappingDescriptionProvider {
056
057        private static final List<HandlerMappingDescriptionProvider<? extends HandlerMapping>> descriptionProviders;
058
059        static {
060                List<HandlerMappingDescriptionProvider<? extends HandlerMapping>> providers = new ArrayList<>();
061                providers.add(new RequestMappingInfoHandlerMappingDescriptionProvider());
062                providers.add(new UrlHandlerMappingDescriptionProvider());
063                if (ClassUtils.isPresent(
064                                "org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping",
065                                null)) {
066                        providers.add(new DelegatingHandlerMappingDescriptionProvider(
067                                        new ArrayList<>(providers)));
068                }
069                descriptionProviders = Collections.unmodifiableList(providers);
070        }
071
072        @Override
073        public String getMappingName() {
074                return "dispatcherServlets";
075        }
076
077        @Override
078        public Map<String, List<DispatcherServletMappingDescription>> describeMappings(
079                        ApplicationContext context) {
080                if (context instanceof WebApplicationContext) {
081                        return describeMappings((WebApplicationContext) context);
082                }
083                return Collections.emptyMap();
084        }
085
086        private Map<String, List<DispatcherServletMappingDescription>> describeMappings(
087                        WebApplicationContext context) {
088                Map<String, List<DispatcherServletMappingDescription>> mappings = new HashMap<>();
089                determineDispatcherServlets(context).forEach((name, dispatcherServlet) -> mappings
090                                .put(name, describeMappings(new DispatcherServletHandlerMappings(name,
091                                                dispatcherServlet, context))));
092                return mappings;
093        }
094
095        private Map<String, DispatcherServlet> determineDispatcherServlets(
096                        WebApplicationContext context) {
097                Map<String, DispatcherServlet> dispatcherServlets = new LinkedHashMap<>();
098                context.getBeansOfType(ServletRegistrationBean.class).values()
099                                .forEach((registration) -> {
100                                        Servlet servlet = registration.getServlet();
101                                        if (servlet instanceof DispatcherServlet
102                                                        && !dispatcherServlets.containsValue(servlet)) {
103                                                dispatcherServlets.put(registration.getServletName(),
104                                                                (DispatcherServlet) servlet);
105                                        }
106                                });
107                context.getBeansOfType(DispatcherServlet.class)
108                                .forEach((name, dispatcherServlet) -> {
109                                        if (!dispatcherServlets.containsValue(dispatcherServlet)) {
110                                                dispatcherServlets.put(name, dispatcherServlet);
111                                        }
112                                });
113                return dispatcherServlets;
114        }
115
116        private List<DispatcherServletMappingDescription> describeMappings(
117                        DispatcherServletHandlerMappings mappings) {
118                return mappings.getHandlerMappings().stream().flatMap(this::describe)
119                                .collect(Collectors.toList());
120        }
121
122        private <T extends HandlerMapping> Stream<DispatcherServletMappingDescription> describe(
123                        T handlerMapping) {
124                return describe(handlerMapping, descriptionProviders).stream();
125        }
126
127        @SuppressWarnings("unchecked")
128        private static <T extends HandlerMapping> List<DispatcherServletMappingDescription> describe(
129                        T handlerMapping,
130                        List<HandlerMappingDescriptionProvider<?>> descriptionProviders) {
131                for (HandlerMappingDescriptionProvider<?> descriptionProvider : descriptionProviders) {
132                        if (descriptionProvider.getMappingClass().isInstance(handlerMapping)) {
133                                return ((HandlerMappingDescriptionProvider<T>) descriptionProvider)
134                                                .describe(handlerMapping);
135                        }
136                }
137                return Collections.emptyList();
138        }
139
140        private interface HandlerMappingDescriptionProvider<T extends HandlerMapping> {
141
142                Class<T> getMappingClass();
143
144                List<DispatcherServletMappingDescription> describe(T handlerMapping);
145
146        }
147
148        private static final class RequestMappingInfoHandlerMappingDescriptionProvider
149                        implements
150                        HandlerMappingDescriptionProvider<RequestMappingInfoHandlerMapping> {
151
152                @Override
153                public Class<RequestMappingInfoHandlerMapping> getMappingClass() {
154                        return RequestMappingInfoHandlerMapping.class;
155                }
156
157                @Override
158                public List<DispatcherServletMappingDescription> describe(
159                                RequestMappingInfoHandlerMapping handlerMapping) {
160                        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping
161                                        .getHandlerMethods();
162                        return handlerMethods.entrySet().stream().map(this::describe)
163                                        .collect(Collectors.toList());
164                }
165
166                private DispatcherServletMappingDescription describe(
167                                Entry<RequestMappingInfo, HandlerMethod> mapping) {
168                        DispatcherServletMappingDetails mappingDetails = new DispatcherServletMappingDetails();
169                        mappingDetails
170                                        .setHandlerMethod(new HandlerMethodDescription(mapping.getValue()));
171                        mappingDetails.setRequestMappingConditions(
172                                        new RequestMappingConditionsDescription(mapping.getKey()));
173                        return new DispatcherServletMappingDescription(mapping.getKey().toString(),
174                                        mapping.getValue().toString(), mappingDetails);
175                }
176
177        }
178
179        private static final class UrlHandlerMappingDescriptionProvider
180                        implements HandlerMappingDescriptionProvider<AbstractUrlHandlerMapping> {
181
182                @Override
183                public Class<AbstractUrlHandlerMapping> getMappingClass() {
184                        return AbstractUrlHandlerMapping.class;
185                }
186
187                @Override
188                public List<DispatcherServletMappingDescription> describe(
189                                AbstractUrlHandlerMapping handlerMapping) {
190                        return handlerMapping.getHandlerMap().entrySet().stream().map(this::describe)
191                                        .collect(Collectors.toList());
192                }
193
194                private DispatcherServletMappingDescription describe(
195                                Entry<String, Object> mapping) {
196                        return new DispatcherServletMappingDescription(mapping.getKey(),
197                                        mapping.getValue().toString(), null);
198                }
199
200        }
201
202        private static final class DelegatingHandlerMappingDescriptionProvider
203                        implements HandlerMappingDescriptionProvider<DelegatingHandlerMapping> {
204
205                private final List<HandlerMappingDescriptionProvider<?>> descriptionProviders;
206
207                private DelegatingHandlerMappingDescriptionProvider(
208                                List<HandlerMappingDescriptionProvider<?>> descriptionProviders) {
209                        this.descriptionProviders = descriptionProviders;
210                }
211
212                @Override
213                public Class<DelegatingHandlerMapping> getMappingClass() {
214                        return DelegatingHandlerMapping.class;
215                }
216
217                @Override
218                public List<DispatcherServletMappingDescription> describe(
219                                DelegatingHandlerMapping handlerMapping) {
220                        List<DispatcherServletMappingDescription> descriptions = new ArrayList<>();
221                        for (HandlerMapping delegate : handlerMapping.getDelegates()) {
222                                descriptions.addAll(DispatcherServletsMappingDescriptionProvider
223                                                .describe(delegate, this.descriptionProviders));
224                        }
225                        return descriptions;
226                }
227
228        }
229
230}