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.jersey; 018 019import java.util.Collections; 020import java.util.EnumSet; 021 022import javax.annotation.PostConstruct; 023import javax.servlet.DispatcherType; 024import javax.servlet.ServletContext; 025import javax.servlet.ServletException; 026import javax.servlet.ServletRegistration; 027import javax.ws.rs.ApplicationPath; 028import javax.ws.rs.ext.ContextResolver; 029 030import com.fasterxml.jackson.databind.AnnotationIntrospector; 031import com.fasterxml.jackson.databind.ObjectMapper; 032import com.fasterxml.jackson.databind.cfg.MapperConfig; 033import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.glassfish.jersey.jackson.JacksonFeature; 037import org.glassfish.jersey.server.ResourceConfig; 038import org.glassfish.jersey.servlet.ServletContainer; 039import org.glassfish.jersey.servlet.ServletProperties; 040 041import org.springframework.beans.factory.ObjectProvider; 042import org.springframework.boot.autoconfigure.AutoConfigureAfter; 043import org.springframework.boot.autoconfigure.AutoConfigureBefore; 044import org.springframework.boot.autoconfigure.AutoConfigureOrder; 045import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 046import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 047import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 048import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 049import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 050import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; 051import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; 052import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; 053import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 054import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; 055import org.springframework.boot.autoconfigure.web.servlet.JerseyApplicationPath; 056import org.springframework.boot.context.properties.EnableConfigurationProperties; 057import org.springframework.boot.web.servlet.DynamicRegistrationBean; 058import org.springframework.boot.web.servlet.FilterRegistrationBean; 059import org.springframework.boot.web.servlet.ServletRegistrationBean; 060import org.springframework.context.annotation.Bean; 061import org.springframework.context.annotation.Configuration; 062import org.springframework.core.Ordered; 063import org.springframework.core.annotation.AnnotationUtils; 064import org.springframework.core.annotation.Order; 065import org.springframework.util.ClassUtils; 066import org.springframework.util.StringUtils; 067import org.springframework.web.WebApplicationInitializer; 068import org.springframework.web.context.ServletContextAware; 069 070/** 071 * {@link EnableAutoConfiguration Auto-configuration} for Jersey. 072 * 073 * @author Dave Syer 074 * @author Andy Wilkinson 075 * @author EddĂș MelĂ©ndez 076 * @author Stephane Nicoll 077 */ 078@Configuration 079@ConditionalOnClass(name = { "org.glassfish.jersey.server.spring.SpringComponentProvider", 080 "javax.servlet.ServletRegistration" }) 081@ConditionalOnBean(type = "org.glassfish.jersey.server.ResourceConfig") 082@ConditionalOnWebApplication(type = Type.SERVLET) 083@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) 084@AutoConfigureBefore(DispatcherServletAutoConfiguration.class) 085@AutoConfigureAfter(JacksonAutoConfiguration.class) 086@EnableConfigurationProperties(JerseyProperties.class) 087public class JerseyAutoConfiguration implements ServletContextAware { 088 089 private static final Log logger = LogFactory.getLog(JerseyAutoConfiguration.class); 090 091 private final JerseyProperties jersey; 092 093 private final ResourceConfig config; 094 095 private final ObjectProvider<ResourceConfigCustomizer> customizers; 096 097 public JerseyAutoConfiguration(JerseyProperties jersey, ResourceConfig config, 098 ObjectProvider<ResourceConfigCustomizer> customizers) { 099 this.jersey = jersey; 100 this.config = config; 101 this.customizers = customizers; 102 } 103 104 @PostConstruct 105 public void path() { 106 customize(); 107 } 108 109 private void customize() { 110 this.customizers.orderedStream() 111 .forEach((customizer) -> customizer.customize(this.config)); 112 } 113 114 @Bean 115 @ConditionalOnMissingBean 116 public JerseyApplicationPath jerseyApplicationPath() { 117 return this::resolveApplicationPath; 118 } 119 120 private String resolveApplicationPath() { 121 if (StringUtils.hasLength(this.jersey.getApplicationPath())) { 122 return this.jersey.getApplicationPath(); 123 } 124 return findApplicationPath(AnnotationUtils.findAnnotation( 125 this.config.getApplication().getClass(), ApplicationPath.class)); 126 } 127 128 @Bean 129 @ConditionalOnMissingBean(name = "jerseyFilterRegistration") 130 @ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "filter") 131 public FilterRegistrationBean<ServletContainer> jerseyFilterRegistration( 132 JerseyApplicationPath applicationPath) { 133 FilterRegistrationBean<ServletContainer> registration = new FilterRegistrationBean<>(); 134 registration.setFilter(new ServletContainer(this.config)); 135 registration.setUrlPatterns( 136 Collections.singletonList(applicationPath.getUrlMapping())); 137 registration.setOrder(this.jersey.getFilter().getOrder()); 138 registration.addInitParameter(ServletProperties.FILTER_CONTEXT_PATH, 139 stripPattern(applicationPath.getPath())); 140 addInitParameters(registration); 141 registration.setName("jerseyFilter"); 142 registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class)); 143 return registration; 144 } 145 146 private String stripPattern(String path) { 147 if (path.endsWith("/*")) { 148 path = path.substring(0, path.lastIndexOf("/*")); 149 } 150 return path; 151 } 152 153 @Bean 154 @ConditionalOnMissingBean(name = "jerseyServletRegistration") 155 @ConditionalOnProperty(prefix = "spring.jersey", name = "type", havingValue = "servlet", matchIfMissing = true) 156 public ServletRegistrationBean<ServletContainer> jerseyServletRegistration( 157 JerseyApplicationPath applicationPath) { 158 ServletRegistrationBean<ServletContainer> registration = new ServletRegistrationBean<>( 159 new ServletContainer(this.config), applicationPath.getUrlMapping()); 160 addInitParameters(registration); 161 registration.setName(getServletRegistrationName()); 162 registration.setLoadOnStartup(this.jersey.getServlet().getLoadOnStartup()); 163 return registration; 164 } 165 166 private String getServletRegistrationName() { 167 return ClassUtils.getUserClass(this.config.getClass()).getName(); 168 } 169 170 private void addInitParameters(DynamicRegistrationBean<?> registration) { 171 this.jersey.getInit().forEach(registration::addInitParameter); 172 } 173 174 private static String findApplicationPath(ApplicationPath annotation) { 175 // Jersey doesn't like to be the default servlet, so map to /* as a fallback 176 if (annotation == null) { 177 return "/*"; 178 } 179 return annotation.value(); 180 } 181 182 @Override 183 public void setServletContext(ServletContext servletContext) { 184 String servletRegistrationName = getServletRegistrationName(); 185 ServletRegistration registration = servletContext 186 .getServletRegistration(servletRegistrationName); 187 if (registration != null) { 188 if (logger.isInfoEnabled()) { 189 logger.info("Configuring existing registration for Jersey servlet '" 190 + servletRegistrationName + "'"); 191 } 192 registration.setInitParameters(this.jersey.getInit()); 193 } 194 } 195 196 @Order(Ordered.HIGHEST_PRECEDENCE) 197 public static final class JerseyWebApplicationInitializer 198 implements WebApplicationInitializer { 199 200 @Override 201 public void onStartup(ServletContext servletContext) throws ServletException { 202 // We need to switch *off* the Jersey WebApplicationInitializer because it 203 // will try and register a ContextLoaderListener which we don't need 204 servletContext.setInitParameter("contextConfigLocation", "<NONE>"); 205 } 206 207 } 208 209 @ConditionalOnClass(JacksonFeature.class) 210 @ConditionalOnSingleCandidate(ObjectMapper.class) 211 @Configuration 212 static class JacksonResourceConfigCustomizer { 213 214 private static final String JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME = "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector"; 215 216 @Bean 217 public ResourceConfigCustomizer resourceConfigCustomizer( 218 final ObjectMapper objectMapper) { 219 addJaxbAnnotationIntrospectorIfPresent(objectMapper); 220 return (ResourceConfig config) -> { 221 config.register(JacksonFeature.class); 222 config.register(new ObjectMapperContextResolver(objectMapper), 223 ContextResolver.class); 224 }; 225 } 226 227 private void addJaxbAnnotationIntrospectorIfPresent(ObjectMapper objectMapper) { 228 if (ClassUtils.isPresent(JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME, 229 getClass().getClassLoader())) { 230 new ObjectMapperCustomizer().addJaxbAnnotationIntrospector(objectMapper); 231 } 232 } 233 234 private static final class ObjectMapperCustomizer { 235 236 private void addJaxbAnnotationIntrospector(ObjectMapper objectMapper) { 237 JaxbAnnotationIntrospector jaxbAnnotationIntrospector = new JaxbAnnotationIntrospector( 238 objectMapper.getTypeFactory()); 239 objectMapper.setAnnotationIntrospectors( 240 createPair(objectMapper.getSerializationConfig(), 241 jaxbAnnotationIntrospector), 242 createPair(objectMapper.getDeserializationConfig(), 243 jaxbAnnotationIntrospector)); 244 } 245 246 private AnnotationIntrospector createPair(MapperConfig<?> config, 247 JaxbAnnotationIntrospector jaxbAnnotationIntrospector) { 248 return AnnotationIntrospector.pair(config.getAnnotationIntrospector(), 249 jaxbAnnotationIntrospector); 250 } 251 252 } 253 254 private static final class ObjectMapperContextResolver 255 implements ContextResolver<ObjectMapper> { 256 257 private final ObjectMapper objectMapper; 258 259 private ObjectMapperContextResolver(ObjectMapper objectMapper) { 260 this.objectMapper = objectMapper; 261 } 262 263 @Override 264 public ObjectMapper getContext(Class<?> type) { 265 return this.objectMapper; 266 } 267 268 } 269 270 } 271 272}