Mercurial > hg > release > thermostat-0.11
changeset 132:4c91cd792089
Implement TimerFactory for centralizing timer tasks and make timer-using code more testable.
author | Roman Kennke <rkennke@redhat.com> |
---|---|
date | Thu, 22 Mar 2012 16:34:49 +0100 |
parents | 997c134b844e |
children | b3b192be55da |
files | client/src/main/java/com/redhat/thermostat/client/Main.java client/src/main/java/com/redhat/thermostat/client/MainWindowFacadeImpl.java client/src/main/java/com/redhat/thermostat/client/appctx/ApplicationContext.java client/src/test/java/com/redhat/thermostat/client/MainWindowFacadeImplTest.java client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextTest.java common/src/main/java/com/redhat/thermostat/common/ThreadPoolTimerFactory.java common/src/main/java/com/redhat/thermostat/common/Timer.java common/src/main/java/com/redhat/thermostat/common/TimerFactory.java common/src/test/java/com/redhat/thermostat/common/ThreadPoolTimerFactoryTest.java |
diffstat | 9 files changed, 486 insertions(+), 16 deletions(-) [+] |
line wrap: on
line diff
--- a/client/src/main/java/com/redhat/thermostat/client/Main.java Wed Mar 21 22:23:11 2012 +0100 +++ b/client/src/main/java/com/redhat/thermostat/client/Main.java Thu Mar 22 16:34:49 2012 +0100 @@ -54,6 +54,8 @@ import com.redhat.thermostat.client.ui.MainWindow; import com.redhat.thermostat.common.Constants; import com.redhat.thermostat.common.LaunchException; +import com.redhat.thermostat.common.ThreadPoolTimerFactory; +import com.redhat.thermostat.common.TimerFactory; import com.redhat.thermostat.common.config.StartupConfiguration; import com.redhat.thermostat.common.dao.Connection; import com.redhat.thermostat.common.dao.Connection.ConnectionListener; @@ -93,6 +95,8 @@ ConnectionProvider connProv = new MongoConnectionProvider(config); DAOFactory daoFactory = new MongoDAOFactory(connProv); ApplicationContext.getInstance().setDAOFactory(daoFactory); + TimerFactory timerFactory = new ThreadPoolTimerFactory(1); + ApplicationContext.getInstance().setTimerFactory(timerFactory); } private void showGui() {
--- a/client/src/main/java/com/redhat/thermostat/client/MainWindowFacadeImpl.java Wed Mar 21 22:23:11 2012 +0100 +++ b/client/src/main/java/com/redhat/thermostat/client/MainWindowFacadeImpl.java Thu Mar 22 16:34:49 2012 +0100 @@ -41,8 +41,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,7 +50,9 @@ import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; +import com.redhat.thermostat.client.appctx.ApplicationContext; import com.redhat.thermostat.client.ui.MainWindow; +import com.redhat.thermostat.common.Timer; import com.redhat.thermostat.common.dao.HostRef; import com.redhat.thermostat.common.dao.VmRef; import com.redhat.thermostat.common.utils.LoggingUtils; @@ -65,7 +65,7 @@ private final DBCollection hostInfoCollection; private final DBCollection vmInfoCollection; - private final Timer backgroundUpdater = new Timer(); + private Timer backgroundUpdater; private MainWindow view; @@ -75,21 +75,32 @@ this.agentConfigCollection = db.getCollection("agent-config"); this.hostInfoCollection = db.getCollection("host-info"); this.vmInfoCollection = db.getCollection("vm-info"); + + initializeTimer(); + } + + private void initializeTimer() { + ApplicationContext ctx = ApplicationContext.getInstance(); + backgroundUpdater = ctx.getTimerFactory().createTimer(); + backgroundUpdater.setAction(new Runnable() { + @Override + public void run() { + doUpdateTreeAsync(); + } + }); + backgroundUpdater.setDelay(0); + backgroundUpdater.setPeriod(10); + backgroundUpdater.setTimeUnit(TimeUnit.SECONDS); } @Override public void start() { - backgroundUpdater.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - doUpdateTreeAsync(); - } - }, 0, TimeUnit.SECONDS.toMillis(10)); + backgroundUpdater.start(); } @Override public void stop() { - backgroundUpdater.cancel(); + backgroundUpdater.stop(); } @Override
--- a/client/src/main/java/com/redhat/thermostat/client/appctx/ApplicationContext.java Wed Mar 21 22:23:11 2012 +0100 +++ b/client/src/main/java/com/redhat/thermostat/client/appctx/ApplicationContext.java Thu Mar 22 16:34:49 2012 +0100 @@ -36,6 +36,7 @@ package com.redhat.thermostat.client.appctx; +import com.redhat.thermostat.common.TimerFactory; import com.redhat.thermostat.common.dao.DAOFactory; public class ApplicationContext { @@ -44,6 +45,8 @@ private DAOFactory daoFactory; + private TimerFactory timerFactory; + public static ApplicationContext getInstance() { return instance; } @@ -65,4 +68,12 @@ return daoFactory; } + public void setTimerFactory(TimerFactory timerFactory) { + this.timerFactory = timerFactory; + } + + public TimerFactory getTimerFactory() { + return timerFactory; + } + }
--- a/client/src/test/java/com/redhat/thermostat/client/MainWindowFacadeImplTest.java Wed Mar 21 22:23:11 2012 +0100 +++ b/client/src/test/java/com/redhat/thermostat/client/MainWindowFacadeImplTest.java Thu Mar 22 16:34:49 2012 +0100 @@ -37,12 +37,11 @@ package com.redhat.thermostat.client; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -51,10 +50,12 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; import com.mongodb.DB; +import com.redhat.thermostat.client.appctx.ApplicationContextUtil; import com.redhat.thermostat.client.ui.MainWindow; +import com.redhat.thermostat.common.Timer; +import com.redhat.thermostat.common.TimerFactory; public class MainWindowFacadeImplTest { @@ -64,17 +65,21 @@ private MainWindow view; + private Timer mainWindowTimer; + @Before public void setUp() { - + ApplicationContextUtil.resetApplicationContext(); DB db = mock(DB.class); controller = new MainWindowFacadeImpl(db); - controller = spy(controller); view = mock(MainWindow.class); ArgumentCaptor<PropertyChangeListener> grabListener = ArgumentCaptor.forClass(PropertyChangeListener.class); doNothing().when(view).addViewPropertyListener(grabListener.capture()); controller.initView(view); l = grabListener.getValue(); + mainWindowTimer = mock(Timer.class); + TimerFactory timerFactory = mock(TimerFactory.class); + when(timerFactory.createTimer()).thenReturn(mainWindowTimer); } @After @@ -82,6 +87,7 @@ view = null; controller = null; l = null; + ApplicationContextUtil.resetApplicationContext(); } @Test @@ -89,7 +95,7 @@ l.propertyChange(new PropertyChangeEvent(view, MainWindow.SHUTDOWN_PROPERTY, false, true)); - verify(controller).stop(); + verify(mainWindowTimer).stop(); }
--- a/client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextTest.java Wed Mar 21 22:23:11 2012 +0100 +++ b/client/src/test/java/com/redhat/thermostat/client/appctx/ApplicationContextTest.java Thu Mar 22 16:34:49 2012 +0100 @@ -44,6 +44,7 @@ import org.junit.Test; import com.redhat.thermostat.client.appctx.ApplicationContext; +import com.redhat.thermostat.common.TimerFactory; import com.redhat.thermostat.common.dao.DAOFactory; public class ApplicationContextTest { @@ -92,4 +93,33 @@ DAOFactory actual2 = ctx2.getDAOFactory(); assertSame(daoFactory, actual2); } + + @Test + public void testTimerFactorySetGet() { + TimerFactory timerFactory = mock(TimerFactory.class); + ApplicationContext ctx = ApplicationContext.getInstance(); + ctx.setTimerFactory(timerFactory); + + TimerFactory actual1 = ctx.getTimerFactory(); + assertSame(timerFactory, actual1); + } + + @Test + public void verifyTimerFactoryIsNullWhenNotInitialized() { + ApplicationContext ctx = ApplicationContext.getInstance(); + + TimerFactory actual = ctx.getTimerFactory(); + assertNull(actual); + } + + @Test + public void verifyTimerFactoryStaysSame() { + TimerFactory timerFactory = mock(TimerFactory.class); + ApplicationContext ctx = ApplicationContext.getInstance(); + ctx.setTimerFactory(timerFactory); + + ApplicationContext ctx2 = ApplicationContext.getInstance(); + TimerFactory actual2 = ctx2.getTimerFactory(); + assertSame(timerFactory, actual2); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/src/main/java/com/redhat/thermostat/common/ThreadPoolTimerFactory.java Thu Mar 22 16:34:49 2012 +0100 @@ -0,0 +1,129 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolTimerFactory implements TimerFactory { + + public ThreadPoolTimerFactory(int poolSize) { + timerThreadPool = Executors.newScheduledThreadPool(poolSize); + } + + private class ThreadPoolTimer implements Timer { + + private Runnable action; + + private long delay; + + private long period; + + private TimeUnit timeUnit = TimeUnit.MILLISECONDS; + + private SchedulingType schedulingType = SchedulingType.ONCE; + + private ScheduledFuture<?> timerTask; + + @Override + public void start() { + if (action != null) { + startScheduling(); + } + } + + private void startScheduling() { + switch (schedulingType) { + case FIXED_RATE: + timerTask = timerThreadPool.scheduleAtFixedRate(action, delay, period, timeUnit); + break; + case FIXED_DELAY: + timerTask = timerThreadPool.scheduleWithFixedDelay(action, delay, period, timeUnit); + break; + case ONCE: + default: + timerTask = timerThreadPool.schedule(action, delay, timeUnit); + } + } + + @Override + public void stop() { + if (timerTask != null) { + timerTask.cancel(false); + } + } + + @Override + public void setAction(Runnable action) { + this.action = action; + } + + @Override + public void setDelay(long delay) { + this.delay = delay; + } + + @Override + public void setPeriod(long period) { + this.period = period; + } + + @Override + public void setSchedulingType(SchedulingType schedulingType) { + if (schedulingType == null) { + throw new NullPointerException(); + } + this.schedulingType = schedulingType; + } + + @Override + public void setTimeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; + } + + } + + private ScheduledExecutorService timerThreadPool; + + @Override + public Timer createTimer() { + return new ThreadPoolTimer(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/src/main/java/com/redhat/thermostat/common/Timer.java Thu Mar 22 16:34:49 2012 +0100 @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common; + +import java.util.concurrent.TimeUnit; + +public interface Timer { + + enum SchedulingType { + ONCE, FIXED_RATE, FIXED_DELAY; + } + + void start(); + void stop(); + void setAction(Runnable action); + void setDelay(long delay); + void setPeriod(long period); + void setSchedulingType(SchedulingType schedulingType); + void setTimeUnit(TimeUnit timeUnit); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/src/main/java/com/redhat/thermostat/common/TimerFactory.java Thu Mar 22 16:34:49 2012 +0100 @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common; + +public interface TimerFactory { + + Timer createTimer(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/common/src/test/java/com/redhat/thermostat/common/ThreadPoolTimerFactoryTest.java Thu Mar 22 16:34:49 2012 +0100 @@ -0,0 +1,183 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.common; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.redhat.thermostat.common.Timer.SchedulingType; + +public class ThreadPoolTimerFactoryTest { + + private Timer timer; + + @Before + public void setUp() { + ThreadPoolTimerFactory timerFactory = new ThreadPoolTimerFactory(1); + timer = timerFactory.createTimer(); + } + + @After + public void tearDown() { + timer = null; + } + + @Test + public void testDefault() throws InterruptedException { + Runnable action = mock(Runnable.class); + timer.setAction(action); + timer.start(); + Thread.sleep(10); + verify(action).run(); + } + + @Test + public void testNullAction() { + timer.setAction(null); + timer.start(); + // Good when no NPE is thrown. + } + + @Test + public void testDefaultWithDelay() throws InterruptedException { + Runnable action = mock(Runnable.class); + timer.setAction(action); + timer.setDelay(50); + timer.start(); + Thread.sleep(10); + verify(action, never()).run(); + Thread.sleep(50); + verify(action).run(); + Thread.sleep(50); + verify(action).run(); + } + + @Test + public void testTimeUnitSecond() throws InterruptedException { + Runnable action = mock(Runnable.class); + timer.setAction(action); + timer.setDelay(1); + timer.setTimeUnit(TimeUnit.SECONDS); + timer.start(); + Thread.sleep(100); + verify(action, never()).run(); + Thread.sleep(1000); + verify(action).run(); + Thread.sleep(50); + verify(action).run(); + } + + @Test(expected=NullPointerException.class) + public void testNullType() throws InterruptedException { + timer.setSchedulingType(null); + } + + @Test + public void testWithFixedRate() throws InterruptedException { + Runnable action = mock(Runnable.class); + doAnswer(new Answer<Void>() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Thread.sleep(45); + return null; + } + + }).when(action).run(); + timer.setAction(action); + timer.setDelay(50); + timer.setPeriod(50); + timer.setSchedulingType(SchedulingType.FIXED_RATE); + timer.start(); + Thread.sleep(10); + verify(action, never()).run(); + Thread.sleep(50); + verify(action).run(); + Thread.sleep(50); + verify(action, times(2)).run(); + timer.stop(); + Thread.sleep(50); + verify(action, times(2)).run(); + } + + @Test + public void testStopWithoutStart() { + timer.stop(); + // Good when no exception is thrown. + } + + @Test + public void testWithFixedDelay() throws InterruptedException { + Runnable action = mock(Runnable.class); + doAnswer(new Answer<Void>() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Thread.sleep(50); + return null; + } + + }).when(action).run(); + timer.setAction(action); + timer.setDelay(50); + timer.setPeriod(50); + timer.setSchedulingType(SchedulingType.FIXED_DELAY); + timer.start(); + Thread.sleep(10); + verify(action, never()).run(); + Thread.sleep(50); + verify(action).run(); + Thread.sleep(50); + verify(action).run(); + Thread.sleep(50); + verify(action, times(2)).run(); + timer.stop(); + Thread.sleep(50); + verify(action, times(2)).run(); + } +}