diff --git a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/Configuration.java b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/Configuration.java index 92af259b..4b8489d2 100644 --- a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/Configuration.java +++ b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/Configuration.java @@ -7,12 +7,13 @@ import org.osgi.service.cm.ManagedService; +@SuppressWarnings( "rawtypes" ) public class Configuration implements ManagedService { static final String CONFIG_SERVICE_PID = "com.eclipsesource.jaxrs.connector"; static final String ROOT_PROPERTY = "root"; static final String WADL_DISABLE_PROPERTY = "disableWadl"; - + static final String PUBLISH_INTERVAL = "publishInterval"; private final JAXRSConnector connector; @@ -21,14 +22,13 @@ public Configuration( JAXRSConnector jaxRsConnector ) { } @Override - @SuppressWarnings( "rawtypes" ) public void updated( Dictionary properties ) throws ConfigurationException { if( properties != null ) { Object root = properties.get( ROOT_PROPERTY ); ensureRootIsPresent( root ); String rootPath = ( String )root; ensureRootIsValid( rootPath ); - connector.updateConfiguration( rootPath, isWadlDisabled( properties ) ); + connector.updateConfiguration( rootPath, isWadlDisabled( properties ), getPublishInterval( properties ) ); } } @@ -44,7 +44,6 @@ private void ensureRootIsPresent( Object root ) throws ConfigurationException { } } - @SuppressWarnings( "rawtypes" ) private boolean isWadlDisabled( Dictionary properties ){ Object wadl = properties.get( WADL_DISABLE_PROPERTY ); if( wadl == null ){ @@ -52,4 +51,12 @@ private boolean isWadlDisabled( Dictionary properties ){ } return ( ( Boolean)wadl ).booleanValue(); } + + private long getPublishInterval( Dictionary properties ) { + Object interval = properties.get( PUBLISH_INTERVAL ); + if( interval == null ){ + return 3000; + } + return Long.parseLong( ( String )interval ); + } } diff --git a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector.java b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector.java index dd9db818..41db18bb 100644 --- a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector.java +++ b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector.java @@ -37,6 +37,7 @@ public class JAXRSConnector { private final List resourceCache; private String rootPath; private boolean isWadlDisabled; + private long publishInterval; JAXRSConnector( BundleContext bundleContext ) { this.bundleContext = bundleContext; @@ -44,17 +45,19 @@ public class JAXRSConnector { this.resources = new ServiceContainer( bundleContext ); this.contextMap = new HashMap(); this.resourceCache = new ArrayList(); + this.publishInterval = 2500; } - void updateConfiguration( String rootPath, boolean isWadlDisabled ) { + void updateConfiguration( String rootPath, boolean isWadlDisabled, long publishInterval ) { synchronized( lock ) { - doUpdateConfiguration( rootPath, isWadlDisabled ); + doUpdateConfiguration( rootPath, isWadlDisabled, publishInterval ); } } - private void doUpdateConfiguration( String rootPath, boolean isWadlDisabled ) { + private void doUpdateConfiguration( String rootPath, boolean isWadlDisabled, long publishInterval ) { this.rootPath = rootPath; this.isWadlDisabled = isWadlDisabled; + this.publishInterval = publishInterval; ServiceHolder[] services = httpServices.getServices(); for( ServiceHolder serviceHolder : services ) { doRemoveHttpService( ( HttpService )serviceHolder.getService() ); @@ -71,7 +74,7 @@ HttpService addHttpService( ServiceReference reference ) { HttpService doAddHttpService( ServiceReference reference ) { ServiceHolder serviceHolder = httpServices.add( reference ); HttpService service = ( HttpService )serviceHolder.getService(); - contextMap.put( service, createJerseyContext( service, rootPath, isWadlDisabled ) ); + contextMap.put( service, createJerseyContext( service, rootPath, isWadlDisabled, publishInterval ) ); clearCache(); return service; } @@ -181,8 +184,8 @@ private void removeResourcesFromContext( Object resource, HttpService httpServic } // For testing purpose - JerseyContext createJerseyContext( HttpService service, String rootPath, boolean disableWadl ) { - return new JerseyContext( service, rootPath, disableWadl ); + JerseyContext createJerseyContext( HttpService service, String rootPath, boolean disableWadl, long publishInterval ) { + return new JerseyContext( service, rootPath, disableWadl, publishInterval ); } } diff --git a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext.java b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext.java index 53772429..56b9d5e9 100644 --- a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext.java +++ b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext.java @@ -12,16 +12,17 @@ ******************************************************************************/ package com.eclipsesource.jaxrs.publisher.internal; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import javax.servlet.ServletException; import javax.ws.rs.core.Application; -import javax.ws.rs.core.Request; -import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.server.ServerProperties; -import org.glassfish.jersey.servlet.ServletContainer; import org.osgi.service.http.HttpService; import org.osgi.service.http.NamespaceException; @@ -29,18 +30,37 @@ public class JerseyContext { private final RootApplication application; - private ServletContainer servletContainer; private final HttpService httpService; private final String rootPath; private boolean isApplicationRegistered; + private final ServletContainerBridge servletContainerBridge; - public JerseyContext( HttpService httpService, String rootPath, boolean isWadlDisabled ) { + public JerseyContext( HttpService httpService, String rootPath, boolean isWadlDisabled, long publishInterval ) { this.httpService = httpService; this.rootPath = rootPath == null ? "/services" : rootPath; this.application = new RootApplication(); disableAutoDiscovery(); disableWadl( isWadlDisabled ); - this.servletContainer = new ServletContainer( ResourceConfig.forApplication( application ) ); + this.servletContainerBridge = new ServletContainerBridge( application ); + scheduleContainerBridge( publishInterval ); + } + + private void scheduleContainerBridge( long publishInterval ) { + Executors.newSingleThreadScheduledExecutor( new ThreadFactory() { + + @Override + public Thread newThread( Runnable runnable ) { + Thread thread = new Thread( runnable, "ServletContainerBridge" ); + thread.setUncaughtExceptionHandler( new UncaughtExceptionHandler() { + + @Override + public void uncaughtException( Thread t, Throwable e ) { + throw new IllegalStateException( e ); + } + } ); + return thread; + } + } ).scheduleAtFixedRate( servletContainerBridge, 1000, publishInterval, TimeUnit.MILLISECONDS ); } private void disableAutoDiscovery() { @@ -64,15 +84,6 @@ private void disableWadl( boolean disableWadl ) { public void addResource( Object resource ) { getRootApplication().addResource( resource ); registerServletWhenNotAlreadyRegistered(); - if( isApplicationRegistered ) { - ClassLoader original = getContextClassloader(); - try { - Thread.currentThread().setContextClassLoader( Request.class.getClassLoader() ); - getServletContainer().reload( ResourceConfig.forApplication( application ) ); - } finally { - resetContextClassloader( original ); - } - } } void registerServletWhenNotAlreadyRegistered() { @@ -109,7 +120,7 @@ private void registerServlet() throws ServletException, NamespaceException { try { Thread.currentThread().setContextClassLoader( Application.class.getClassLoader() ); httpService.registerServlet( rootPath, - getServletContainer(), + servletContainerBridge.getServletContainer(), null, null ); } finally { @@ -123,22 +134,19 @@ private void resetContextClassloader( ClassLoader loader ) { public void removeResource( Object resource ) { getRootApplication().removeResource( resource ); - if( isApplicationRegistered ) { - getServletContainer().reload( ResourceConfig.forApplication( application ) ); - } unregisterServletWhenNoresourcePresents(); } private void unregisterServletWhenNoresourcePresents() { if( !getRootApplication().hasResources() ) { httpService.unregister( rootPath ); - servletContainer = new ServletContainer( ResourceConfig.forApplication( application ) ); + servletContainerBridge.reset(); isApplicationRegistered = false; } } public List eliminate() { - getServletContainer().destroy(); + servletContainerBridge.destroy(); try { httpService.unregister( rootPath ); } catch( IllegalArgumentException iae ) { @@ -147,11 +155,6 @@ public List eliminate() { return new ArrayList( getRootApplication().getSingletons() ); } - // For testing purpose - ServletContainer getServletContainer() { - return servletContainer; - } - // For testing purpose RootApplication getRootApplication() { return application; diff --git a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/RootApplication.java b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/RootApplication.java index 40984b1e..0156f4fc 100644 --- a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/RootApplication.java +++ b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/RootApplication.java @@ -25,6 +25,8 @@ public class RootApplication extends Application { private final Map properties; private final List resources; + private final Object lock = new Object(); + private boolean dirty; public RootApplication() { resources = new LinkedList(); @@ -32,11 +34,17 @@ public RootApplication() { } void addResource( Object resource ) { - resources.add( resource ); + synchronized( lock ) { + resources.add( resource ); + dirty = true; + } } void removeResource( Object resource ) { - resources.remove( resource ); + synchronized( lock ) { + resources.remove( resource ); + dirty = false; + } } boolean hasResources() { @@ -58,5 +66,17 @@ public Map getProperties() { public void addProperty( String key, Object value ) { properties.put( key, value ); } + + public boolean isDirty() { + synchronized( lock ) { + return dirty; + } + } + + public void setDirty( boolean isDirty ) { + synchronized( lock ) { + dirty = isDirty; + } + } } diff --git a/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge.java b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge.java new file mode 100644 index 00000000..474b4cb9 --- /dev/null +++ b/bundles/com.eclipsesource.jaxrs.publisher/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge.java @@ -0,0 +1,44 @@ +package com.eclipsesource.jaxrs.publisher.internal; + +import javax.ws.rs.core.Request; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; + + +public class ServletContainerBridge implements Runnable { + + private final RootApplication application; + private ServletContainer servletContainer; + + public ServletContainerBridge( RootApplication application ) { + this.servletContainer = new ServletContainer( ResourceConfig.forApplication( application ) ); + this.application = application; + } + + @Override + public void run() { + if( application.isDirty() ) { + ClassLoader original = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader( Request.class.getClassLoader() ); + getServletContainer().reload( ResourceConfig.forApplication( application ) ); + } finally { + Thread.currentThread().setContextClassLoader( original ); + application.setDirty( false ); + } + } + } + + public ServletContainer getServletContainer() { + return servletContainer; + } + + public void destroy() { + servletContainer.destroy(); + } + + public void reset() { + this.servletContainer = new ServletContainer( ResourceConfig.forApplication( application ) ); + } +} diff --git a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/Configuration_Test.java b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/Configuration_Test.java index f071aab9..249c3ce9 100644 --- a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/Configuration_Test.java +++ b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/Configuration_Test.java @@ -1,10 +1,11 @@ package com.eclipsesource.jaxrs.publisher.internal; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.eq; import java.util.Dictionary; import java.util.Hashtable; @@ -29,28 +30,35 @@ public void setUp() { public void testUpdateWithNull() throws Exception { config.updated( null ); - verify( connector, never() ).updateConfiguration( anyString(), eq(false)); + verify( connector, never() ).updateConfiguration( anyString(), eq(false), anyLong() ); } @Test public void testUpdateWithPath() throws Exception { config.updated( createProperties( "/test" ) ); - verify( connector ).updateConfiguration( "/test" , false); + verify( connector ).updateConfiguration( "/test" , false, 4 ); } @Test public void testUpdateWithPath2() throws Exception { config.updated( createProperties( "/test2" ) ); - verify( connector ).updateConfiguration( "/test2" , false ); + verify( connector ).updateConfiguration( "/test2" , false, 4 ); } @Test public void testUpdateWithDisabledWadl() throws Exception { config.updated( createProperties( "/test", true ) ); - verify( connector ).updateConfiguration( "/test" , true ); + verify( connector ).updateConfiguration( "/test" , true, 4 ); + } + + @Test + public void testUpdateWithPublishInterval() throws Exception { + config.updated( createProperties( "/test", true ) ); + + verify( connector ).updateConfiguration( "/test" , true, 4 ); } @Test( expected = ConfigurationException.class ) @@ -71,6 +79,7 @@ public void testUpdateWithEmptyProperties() throws Exception { Hashtable properties = new Hashtable(); properties.put( Configuration.ROOT_PROPERTY, path ); properties.put( Configuration.WADL_DISABLE_PROPERTY, disableWadl ); + properties.put( Configuration.PUBLISH_INTERVAL, "4" ); return properties; } diff --git a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector_Test.java b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector_Test.java index f7dd1476..f7e6cc1f 100644 --- a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector_Test.java +++ b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JAXRSConnector_Test.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertSame; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; @@ -54,7 +55,7 @@ public class JAXRSConnector_Test { public void setUp() { JAXRSConnector original = new JAXRSConnector( bundleContext ); connector = spy( original ); - doReturn( jerseyContext ).when( connector ).createJerseyContext( any( HttpService.class ), anyString(), eq( false ) ); + doReturn( jerseyContext ).when( connector ).createJerseyContext( any( HttpService.class ), anyString(), eq( false ), anyLong() ); } @Test @@ -116,13 +117,13 @@ public void testUpdatePath() { connector.addResource( resourceServiceReference ); connector.addHttpService( httpServiceReference ); - connector.updateConfiguration( "/test", false ); + connector.updateConfiguration( "/test", false, 23 ); InOrder order = inOrder( connector ); - order.verify( connector ).createJerseyContext( any( HttpService.class ), anyString(), eq( false ) ); + order.verify( connector ).createJerseyContext( any( HttpService.class ), anyString(), eq( false ), eq( 2500L ) ); order.verify( connector ).doRemoveHttpService( any( HttpService.class ) ); order.verify( connector ).doAddHttpService( any( ServiceReference.class ) ); - order.verify( connector ).createJerseyContext( any( HttpService.class ), eq( "/test" ), eq( false ) ); + order.verify( connector ).createJerseyContext( any( HttpService.class ), eq( "/test" ), eq( false ), eq( 23L ) ); } @Test diff --git a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext_Test.java b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext_Test.java index aa72d385..9b4e8fda 100644 --- a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext_Test.java +++ b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/JerseyContext_Test.java @@ -13,6 +13,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; @@ -54,9 +55,8 @@ public class JerseyContext_Test { @Before public void setUp() { - JerseyContext original = new JerseyContext( httpService, "/test", false ); + JerseyContext original = new JerseyContext( httpService, "/test", false, 23 ); jerseyContext = spy( original ); - doReturn( servletContainer ).when( jerseyContext ).getServletContainer(); doReturn( rootApplication ).when( jerseyContext ).getRootApplication(); } @@ -67,7 +67,7 @@ public void testAddResource() throws ServletException, NamespaceException { jerseyContext.addResource( resource ); verify( rootApplication ).addResource( resource ); - verify( httpService ).registerServlet( "/test", servletContainer, null, null ); + verify( httpService ).registerServlet( eq( "/test" ), any( ServletContainer.class ), any( Dictionary.class ), any( HttpContext.class ) ); } @Test @@ -80,7 +80,7 @@ public void testRemoveSingleResource() throws ServletException, NamespaceExcepti verify( rootApplication ).addResource( resource ); verify( rootApplication ).removeResource( resource ); - verify( httpService ).registerServlet( "/test", servletContainer, null, null ); + verify( httpService ).registerServlet( eq( "/test" ), any( ServletContainer.class ), any( Dictionary.class ), any( HttpContext.class ) ); verify( httpService ).unregister( "/test" ); } @@ -98,11 +98,10 @@ public void testRemoveResource() throws ServletException, NamespaceException { verify( rootApplication ).addResource( resource ); verify( rootApplication ).addResource( resource2 ); verify( rootApplication ).removeResource( resource ); - verify( httpService ).registerServlet( "/test", servletContainer, null, null ); + verify( httpService ).registerServlet( eq( "/test" ), any( ServletContainer.class ), any( Dictionary.class ), any( HttpContext.class ) ); verify( httpService, never() ).unregister( "/test" ); } - @Test public void testEliminate() throws ServletException, NamespaceException { Object resource = new Object(); @@ -115,7 +114,6 @@ public void testEliminate() throws ServletException, NamespaceException { verify( rootApplication ).addResource( resource ); verify( httpService ).unregister( "/test" ); - verify( servletContainer ).destroy(); assertEquals( 1, resources.size() ); assertEquals( resource, resources.get( 0 ) ); } @@ -143,7 +141,7 @@ public void testConvertsNamespaceException() throws ServletException, NamespaceE @Test public void testDoesNotRegster_METAINF_SERVICES_LOOKUP_DISABLE() { - JerseyContext context = new JerseyContext( httpService, "/test", false ); + JerseyContext context = new JerseyContext( httpService, "/test", false, 23 ); Map properties = context.getRootApplication().getProperties(); @@ -152,7 +150,7 @@ public void testDoesNotRegster_METAINF_SERVICES_LOOKUP_DISABLE() { @Test public void testRegsters_FEATURE_AUTO_DISCOVERY_DISABLE() { - JerseyContext context = new JerseyContext( httpService, "/test" , false); + JerseyContext context = new JerseyContext( httpService, "/test", false, 23 ); Map properties = context.getRootApplication().getProperties(); @@ -161,7 +159,7 @@ public void testRegsters_FEATURE_AUTO_DISCOVERY_DISABLE() { @Test public void testRegsters_WADL_FEATURE_DISABLE() { - JerseyContext context = new JerseyContext( httpService, "/test" , true); + JerseyContext context = new JerseyContext( httpService, "/test", true, 23); Map properties = context.getRootApplication().getProperties(); diff --git a/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge_Test.java b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge_Test.java new file mode 100644 index 00000000..99a97b49 --- /dev/null +++ b/tests/com.eclipsesource.jaxrs.publisher.test/src/com/eclipsesource/jaxrs/publisher/internal/ServletContainerBridge_Test.java @@ -0,0 +1,94 @@ +package com.eclipsesource.jaxrs.publisher.internal; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; +import org.junit.Before; +import org.junit.Test; + + +public class ServletContainerBridge_Test { + + private RootApplication application; + private ServletContainerBridge bridge; + + @Before + public void setUp() { + application = mock( RootApplication.class ); + bridge = new ServletContainerBridge( application ); + } + + @Test + public void testCreateServletContainer() { + ServletContainer container = bridge.getServletContainer(); + + assertNotNull( container ); + } + + @Test + public void testCreateServletContainerOnce() { + ServletContainer container = bridge.getServletContainer(); + ServletContainer container2 = bridge.getServletContainer(); + + assertSame( container, container2 ); + } + + @Test + public void testResetCreatesNewContainer() { + ServletContainer container = bridge.getServletContainer(); + bridge.reset(); + ServletContainer container2 = bridge.getServletContainer(); + + assertNotSame( container, container2 ); + } + + @Test + public void testCallsDestroyDoesNotFail() { + bridge.destroy(); + } + + @Test + public void testRunReloadsOnDirty() { + ServletContainerBridge actualBridge = spy( bridge ); + ServletContainer container = mock( ServletContainer.class ); + when( actualBridge.getServletContainer() ).thenReturn( container ); + when( application.isDirty() ).thenReturn( true ); + + actualBridge.run(); + + verify( container ).reload( any( ResourceConfig.class ) ); + } + + @Test + public void testRunReloadsOnlyOnDirty() { + ServletContainerBridge actualBridge = spy( bridge ); + ServletContainer container = mock( ServletContainer.class ); + when( actualBridge.getServletContainer() ).thenReturn( container ); + when( application.isDirty() ).thenReturn( false ); + + actualBridge.run(); + + verify( container, never() ).reload( any( ResourceConfig.class ) ); + } + + @Test + public void testRunSetsDirtyToFalseAfterRun() { + ServletContainerBridge actualBridge = spy( bridge ); + ServletContainer container = mock( ServletContainer.class ); + when( actualBridge.getServletContainer() ).thenReturn( container ); + when( application.isDirty() ).thenReturn( true ); + + actualBridge.run(); + + verify( application ).setDirty( false ); + } +}