/* * Copyright 2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.util.http; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.netty.util.internal.ConcurrentHashMap; import org.junit.Assert; import org.mortbay.jetty.Server; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.http.converter.obm.MarshallingHttpMessageConverter; import org.springframework.http.converter.obm.support.DebugClientHttpRequestInterceptor; import org.springframework.obm.Marshaller; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.Arrays; import java.util.List; import java.util.Map; /** * It turns out that setting up a working server to integration test an object * is pretty tedious for a unit test and most of the code is invariant. so the goal * here is to extract the variants and make that easy to setup going forward. * * @author Josh Long */ public class RestIntegrationTestUtils { public static interface ServerExecutionCallback { void doWithServer(RestTemplate restTemplate, Server server) throws Throwable; } static Log log = LogFactory.getLog(RestIntegrationTestUtils.class); static private Map<AbstractRestServiceConfiguration, BeanFactory> beanFactoryMap = new ConcurrentHashMap<AbstractRestServiceConfiguration, BeanFactory>(); public static boolean stopServerQuietly(Server server) throws Throwable { Assert.assertNotNull(server); if (!server.isStopped()) { server.stop(); } while (!server.isStopped()) { Thread.sleep(500); } return true; } /** * passes classes of {@link AbstractRestServiceConfiguration} to {@link EndpointTestUtils#serve(JettyContextConfigurationCallback)} * and waits for the {@link Server} to start. To make sure that the we can obtain a pointer to the {@link RestTemplate} that * was configured in {@link org.springframework.context.annotation.Configuration configuration class}, we implant the Spring * context with a bean tht simply exports the {@link ApplicationContext} to a well known variable, {@link #beanFactoryMap}, * where this code then looks it up and returns it to the client. * * @param clazz the configuration class * @throws Throwable should an exception be thrown */ public static void startServiceAndConnect(Class<? extends AbstractRestServiceConfiguration> clazz, ServerExecutionCallback c) throws Throwable { DispatcherServletJettyConfigurationCallback configurationCallback = new DispatcherServletJettyConfigurationCallback(clazz); Server server = EndpointTestUtils.serve(configurationCallback); server.start(); org.springframework.util.Assert.isTrue(beanFactoryMap.size() == 1, "there should only be one entry in this map."); BeanFactory beanFactory = beanFactoryMap.values().iterator().next(); beanFactoryMap.clear(); Assert.assertNotNull(beanFactory); RestTemplate restTemplate = beanFactory.getBean(RestTemplate.class); Assert.assertNotNull(restTemplate); try { c.doWithServer(restTemplate, server); } catch (AssertionError ae) { throw ae; } catch (Throwable th) { if (log.isErrorEnabled()) { log.error("something went wrong in execution the callback ", th); } } finally { stopServerQuietly(server); } } /** * Abstract template class. Clients may extend this class and then fill out the * definitions for the salient parts. * * @author Josh Long */ public abstract static class AbstractRestServiceConfiguration extends WebMvcConfigurerAdapter { abstract public Marshaller getMarshaller(); abstract public MediaType getMediaType(); @Bean // hackety hackety public ApplicationContextAware applicationContextExporter() { return new ApplicationContextAware() { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { beanFactoryMap.put(AbstractRestServiceConfiguration.this, applicationContext); } }; } @Bean public Marshaller marshaller() { return getMarshaller(); } @Bean public MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() { MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter(); mappingJacksonHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON)); return mappingJacksonHttpMessageConverter; } @Bean public HttpMessageConverter messageConverter() { MarshallingHttpMessageConverter mc = new MarshallingHttpMessageConverter(this.marshaller()); mc.setSupportedMediaTypes(Arrays.asList(getMediaType())); return mc; } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(messageConverter()); converters.add(mappingJacksonHttpMessageConverter()); } @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setMessageConverters(Arrays.<HttpMessageConverter<?>>asList(messageConverter())); restTemplate.setInterceptors(new ClientHttpRequestInterceptor[]{new DebugClientHttpRequestInterceptor(getMediaType())}); return restTemplate; } } }