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