diff --git a/.gitignore b/.gitignore index 0199fd9..4b758cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/thermostat-1.6.4.tar.gz +SOURCES/thermostat-1.6.6.tar.gz diff --git a/.rh-thermostat16-thermostat.metadata b/.rh-thermostat16-thermostat.metadata index 95f65d9..c45b3c2 100644 --- a/.rh-thermostat16-thermostat.metadata +++ b/.rh-thermostat16-thermostat.metadata @@ -1 +1 @@ -647fd31c639f65b7f7cf1f9cd1deaece58a3b0b0 SOURCES/thermostat-1.6.4.tar.gz +c1d19848af007e8cceb22e0839feacaaeffb40cb SOURCES/thermostat-1.6.6.tar.gz diff --git a/SOURCES/0003_storage_init_fix.patch b/SOURCES/0003_storage_init_fix.patch deleted file mode 100644 index 2dd0ffa..0000000 --- a/SOURCES/0003_storage_init_fix.patch +++ /dev/null @@ -1,601 +0,0 @@ -# HG changeset patch -# User Andrew Azores -# Date 1474381585 14400 -# Tue Sep 20 10:26:25 2016 -0400 -# Node ID 7a1c62f9337becc320476d4172025f24021da3e9 -# Parent d3a72ee75e2634dfeb996c151fb6d68b68f23f9b -Ensure storage is only initialized once in WebStorageEndPoint - -PR3170 -Reviewed-by: jerboaa -Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-September/020830.html - -diff --git a/web/server/pom.xml b/web/server/pom.xml ---- a/web/server/pom.xml -+++ b/web/server/pom.xml -@@ -63,6 +63,17 @@ - test - - -+ org.powermock -+ powermock-api-mockito -+ test -+ -+ -+ org.powermock -+ powermock-module-junit4 -+ test -+ -+ -+ - org.eclipse.jetty - jetty-server - ${jetty.version} -diff --git a/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactory.java b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactory.java ---- a/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactory.java -+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactory.java -@@ -34,50 +34,14 @@ - * to do so, delete this exception statement from your version. - */ - -- - package com.redhat.thermostat.web.server; - - import com.redhat.thermostat.shared.config.CommonPaths; --import com.redhat.thermostat.shared.config.SSLConfiguration; --import com.redhat.thermostat.shared.config.internal.SSLConfigurationImpl; - import com.redhat.thermostat.storage.core.Storage; - import com.redhat.thermostat.storage.core.StorageCredentials; --import com.redhat.thermostat.storage.core.StorageProvider; --import com.redhat.thermostat.storage.mongodb.MongoStorageProvider; - --class StorageFactory { -+interface StorageFactory { - -- private static Storage storage; -+ Storage getStorage(String storageClass, String storageEndpoint, CommonPaths paths, StorageCredentials creds); - -- // Web server is not OSGi, this factory method is workaround. -- static Storage getStorage(String storageClass, final String storageEndpoint, final CommonPaths paths, -- final StorageCredentials creds) { -- if (storage != null) { -- return storage; -- } -- SSLConfiguration sslConf = new SSLConfigurationImpl(paths); -- try { -- StorageProvider provider = (StorageProvider) Class.forName(storageClass).newInstance(); -- provider.setConfig(storageEndpoint, creds, sslConf); -- storage = provider.createStorage(); -- storage.getConnection().connect(); -- return storage; -- } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { -- // This fallback should infact not be used. But it gives us an automatic -- // Import-Package in the OSGi descriptor, which actually *prevents* this same -- // exception from happening (a recursive self-defeating catch-block) :-) -- System.err.println("could not instantiate provider: " + storageClass + ", falling back to MongoStorage"); -- e.printStackTrace(); -- StorageProvider provider = new MongoStorageProvider(); -- provider.setConfig(storageEndpoint, creds, sslConf); -- storage = provider.createStorage(); -- return storage; -- } -- } -- -- // Testing hook used in WebStorageEndpointTest -- static void setStorage(Storage storage) { -- StorageFactory.storage = storage; -- } - } -- -diff --git a/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryImpl.java b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryImpl.java -new file mode 100644 ---- /dev/null -+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryImpl.java -@@ -0,0 +1,83 @@ -+/* -+ * Copyright 2012-2016 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 -+ * . -+ * -+ * 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.web.server; -+ -+import com.redhat.thermostat.shared.config.CommonPaths; -+import com.redhat.thermostat.shared.config.SSLConfiguration; -+import com.redhat.thermostat.shared.config.internal.SSLConfigurationImpl; -+import com.redhat.thermostat.storage.core.Storage; -+import com.redhat.thermostat.storage.core.StorageCredentials; -+import com.redhat.thermostat.storage.core.StorageProvider; -+import com.redhat.thermostat.storage.mongodb.MongoStorageProvider; -+ -+class StorageFactoryImpl { -+ -+ private static Storage storage; -+ -+ // Web server is not OSGi, this factory method is workaround. -+ static Storage getStorage(String storageClass, final String storageEndpoint, final CommonPaths paths, -+ final StorageCredentials creds) { -+ if (storage != null) { -+ return storage; -+ } -+ SSLConfiguration sslConf = new SSLConfigurationImpl(paths); -+ try { -+ StorageProvider provider = (StorageProvider) Class.forName(storageClass).newInstance(); -+ provider.setConfig(storageEndpoint, creds, sslConf); -+ storage = provider.createStorage(); -+ storage.getConnection().connect(); -+ return storage; -+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { -+ // This fallback should infact not be used. But it gives us an automatic -+ // Import-Package in the OSGi descriptor, which actually *prevents* this same -+ // exception from happening (a recursive self-defeating catch-block) :-) -+ System.err.println("could not instantiate provider: " + storageClass + ", falling back to MongoStorage"); -+ e.printStackTrace(); -+ StorageProvider provider = new MongoStorageProvider(); -+ provider.setConfig(storageEndpoint, creds, sslConf); -+ storage = provider.createStorage(); -+ return storage; -+ } -+ } -+ -+ // Testing hook used in WebStorageEndpointTest -+ static void setStorage(Storage storage) { -+ StorageFactoryImpl.storage = storage; -+ } -+} -+ -diff --git a/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryProvider.java b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryProvider.java -new file mode 100644 ---- /dev/null -+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/StorageFactoryProvider.java -@@ -0,0 +1,43 @@ -+/* -+ * Copyright 2012-2016 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 -+ * . -+ * -+ * 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.web.server; -+ -+interface StorageFactoryProvider { -+ -+ StorageFactory createStorageFactory(); -+ -+} -diff --git a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java ---- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java -+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java -@@ -145,6 +145,8 @@ - - private static final Logger logger = LoggingUtils.getLogger(WebStorageEndPoint.class); - -+ private final Object storageLock = new Object(); -+ private StorageFactoryProvider storageFactoryProvider; - private Storage storage; - private Gson gson; - private CommonPaths paths; -@@ -164,14 +166,27 @@ - private TimerRegistry timerRegistry; - - public WebStorageEndPoint() { -- // default constructor -+ // this ugliness allows for both unit tests to inject mocks and "integration" tests to inject fake values -+ this.storageFactoryProvider = new StorageFactoryProvider() { -+ @Override -+ public StorageFactory createStorageFactory() { -+ return new StorageFactory() { -+ @Override -+ public Storage getStorage(String storageClass, String storageEndpoint, CommonPaths paths, StorageCredentials creds) { -+ return StorageFactoryImpl.getStorage(storageClass, storageEndpoint, paths, creds); -+ } -+ }; -+ } -+ }; - } - - // Package private for testing -- WebStorageEndPoint(TimerRegistry timerRegistry, CommonPaths paths, ConfigurationFinder finder) { -+ WebStorageEndPoint(TimerRegistry timerRegistry, CommonPaths paths, ConfigurationFinder finder, -+ StorageFactoryProvider storageFactoryProvider) { - this.timerRegistry = timerRegistry; - this.paths = paths; - this.finder = finder; -+ this.storageFactoryProvider = storageFactoryProvider; - } - - @Override -@@ -222,41 +237,52 @@ - servletContext.setAttribute(PREPARED_STMT_MANAGER_KEY, new PreparedStatementManager()); - servletContext.setAttribute(SERVER_TOKEN_KEY, UUID.randomUUID()); - } -+ -+ synchronized (storageLock) { -+ if (storage == null) { -+ StorageCredentials creds; -+ try { -+ creds = getStorageCredentials(); -+ } catch (IOException e) { -+ String errorMsg = "Unable to retrieve backing storage credentials from file " + CREDENTIALS_FILE; -+ throw new InvalidConfigurationException(errorMsg); -+ } -+ // if creds are null there is no point to continue, fail prominently. -+ if (creds == null) { -+ String errorMsg = "No backing storage credentials file (" + CREDENTIALS_FILE + ") available"; -+ throw new InvalidConfigurationException(errorMsg); -+ } -+ String storageClass = getServletConfig().getInitParameter(STORAGE_CLASS); -+ String storageEndpoint = getServletConfig().getInitParameter(STORAGE_ENDPOINT); -+ storage = storageFactoryProvider.createStorageFactory().getStorage(storageClass, storageEndpoint, paths, creds); -+ } -+ } - } - - @Override - public void destroy() { - timerRegistry.shutDown(); -- logger.log(Level.INFO, "Going to shut down web service"); -- if (storage != null) { -- // See IcedTea BZ#1315. Shut down storage in order -- // to avoid further memory leaks. -- Connection connection = storage.getConnection(); -- try { -- // Tests have null connections -- if (connection != null) { -- connection.disconnect(); -+ synchronized (storageLock) { -+ logger.log(Level.INFO, "Going to shut down web service"); -+ if (storage != null) { -+ // See IcedTea BZ#1315. Shut down storage in order -+ // to avoid further memory leaks. -+ Connection connection = storage.getConnection(); -+ try { -+ // Tests have null connections -+ if (connection != null) { -+ connection.disconnect(); -+ } -+ } finally { -+ storage.shutdown(); - } -- } finally { -- storage.shutdown(); - } -+ logger.log(Level.INFO, "Web service shut down finished"); - } -- logger.log(Level.INFO, "Web service shut down finished"); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { -- if (storage == null) { -- StorageCredentials creds = getStorageCredentials(); -- // if creds are null there is no point to continue, fail prominently. -- if (creds == null) { -- String errorMsg = "No backing storage credentials file (" + CREDENTIALS_FILE + ") available"; -- throw new InvalidConfigurationException(errorMsg); -- } -- String storageClass = getServletConfig().getInitParameter(STORAGE_CLASS); -- String storageEndpoint = getServletConfig().getInitParameter(STORAGE_ENDPOINT); -- storage = StorageFactory.getStorage(storageClass, storageEndpoint, paths, creds); -- } - String uri = req.getRequestURI(); - int lastPartIdx = uri.lastIndexOf("/"); - String cmd = uri.substring(lastPartIdx + 1); -@@ -413,8 +439,9 @@ - // PreparedStatementManager - PreparedStatement targetPreparedStatement; - try { -- targetPreparedStatement = (PreparedStatement) storage -- .prepareStatement(desc); -+ synchronized (storageLock) { -+ targetPreparedStatement = storage.prepareStatement(desc); -+ } - } catch (DescriptorParsingException e) { - logger.log(Level.WARNING, "Descriptor parse error!", e); - SharedStateId id = new SharedStateId(WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED, serverToken); -@@ -452,7 +479,9 @@ - } - - String agentId = req.getParameter("agentId"); -- storage.purge(agentId); -+ synchronized (storageLock) { -+ storage.purge(agentId); -+ } - resp.setStatus(HttpServletResponse.SC_OK); - } - -@@ -466,21 +495,23 @@ - if (! isAllowedToLoadFile(req, resp, name)) { - return; - } -- try (InputStream data = storage.loadFile(name)) { -- if (data == null) { -- resp.setStatus(HttpServletResponse.SC_NO_CONTENT); -- return; -+ synchronized (storageLock) { -+ try (InputStream data = storage.loadFile(name)) { -+ if (data == null) { -+ resp.setStatus(HttpServletResponse.SC_NO_CONTENT); -+ return; -+ } -+ OutputStream out = resp.getOutputStream(); -+ byte[] buffer = new byte[512]; -+ int read = 0; -+ while (read >= 0) { -+ read = data.read(buffer); -+ if (read > 0) { -+ out.write(buffer, 0, read); -+ } -+ } -+ resp.setStatus(HttpServletResponse.SC_OK); - } -- OutputStream out = resp.getOutputStream(); -- byte[] buffer = new byte[512]; -- int read = 0; -- while (read >= 0) { -- read = data.read(buffer); -- if (read > 0) { -- out.write(buffer, 0, read); -- } -- } -- resp.setStatus(HttpServletResponse.SC_OK); - } - } - -@@ -507,7 +538,9 @@ - return; - } - InputStream in = item.getInputStream(); -- storage.saveFile(name, in); -+ synchronized (storageLock) { -+ storage.saveFile(name, in); -+ } - } - } - } catch (FileUploadException ex) { -@@ -591,7 +624,9 @@ - // The following has the side effect of registering the newly - // deserialized Category in the Categories class. - category = gson.fromJson(categoryParam, Category.class); -- storage.registerCategory(category); -+ synchronized (storageLock) { -+ storage.registerCategory(category); -+ } - } - id = catManager.putCategory(getServerToken(), category, catIdentifier); - if (isAggregateCat) { -diff --git a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndPointUnitTest.java b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndPointUnitTest.java ---- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndPointUnitTest.java -+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndPointUnitTest.java -@@ -42,10 +42,14 @@ - import static org.junit.Assert.assertNull; - import static org.junit.Assert.assertTrue; - import static org.junit.Assert.fail; -+import static org.mockito.Matchers.any; -+import static org.mockito.Matchers.anyString; - import static org.mockito.Mockito.mock; -+import static org.mockito.Mockito.verifyZeroInteractions; - import static org.mockito.Mockito.when; - import static org.mockito.Mockito.verify; - import static org.mockito.Matchers.eq; -+import static org.powermock.api.mockito.PowerMockito.whenNew; - - import java.io.File; - import java.io.IOException; -@@ -61,13 +65,18 @@ - import javax.servlet.ServletContext; - import javax.servlet.ServletException; - -+import com.redhat.thermostat.storage.core.Storage; - import org.junit.After; -+import org.junit.Before; - import org.junit.Test; -+import org.junit.runner.RunWith; - import org.mockito.ArgumentCaptor; - - import com.redhat.thermostat.shared.config.CommonPaths; - import com.redhat.thermostat.storage.core.StorageCredentials; - import com.redhat.thermostat.web.server.auth.WebStoragePathHandler; -+import org.powermock.core.classloader.annotations.PrepareForTest; -+import org.powermock.modules.junit4.PowerMockRunner; - - /** - * A test class for {@link WebStorageEndPoint}. It should contain tests for -@@ -75,9 +84,26 @@ - * more of the unit-test nature (rather than of the functional test nature). - * - */ -+@RunWith(PowerMockRunner.class) - public class WebStorageEndPointUnitTest { - - private static final String TH_HOME_PROP_NAME = "THERMOSTAT_HOME"; -+ -+ private StorageFactoryProvider storageFactoryProvider; -+ private StorageFactory storageFactory; -+ private Storage storage; -+ -+ @Before -+ public void setup() { -+ storage = mock(Storage.class); -+ storageFactory = mock(StorageFactory.class); -+ storageFactoryProvider = mock(StorageFactoryProvider.class); -+ -+ when(storageFactoryProvider.createStorageFactory()).thenReturn(storageFactory); -+ when(storageFactory.getStorage(anyString(), anyString(), any(CommonPaths.class), any(StorageCredentials.class))) -+ .thenReturn(storage); -+ } -+ - @After - public void tearDown() { - System.clearProperty(TH_HOME_PROP_NAME); -@@ -217,17 +243,27 @@ - * @throws IOException - */ - @Test -- public void testSetServletAttribute() throws ServletException, IOException { -+ @PrepareForTest({ WebStorageEndPoint.class }) -+ public void testSetServletAttribute() throws Exception { - final ServletContext mockContext = mock(ServletContext.class); - when(mockContext.getServerInfo()).thenReturn("jetty/9.1.0.v20131115"); -+ final ConfigurationFinder finder = mock(ConfigurationFinder.class); -+ when(finder.getConfiguration(anyString())).thenReturn(mock(File.class)); -+ FileBasedStorageCredentials mockCreds = mock(FileBasedStorageCredentials.class); -+ when(mockCreds.getUsername()).thenReturn("username"); -+ when(mockCreds.getPassword()).thenReturn("password".toCharArray()); -+ whenNew(FileBasedStorageCredentials.class).withAnyArguments().thenReturn(mockCreds); - @SuppressWarnings("serial") -- WebStorageEndPoint endpoint = new WebStorageEndPoint() { -+ WebStorageEndPoint endpoint = new WebStorageEndPoint(null, null, finder, storageFactoryProvider) { - @Override - public ServletContext getServletContext() { - return mockContext; - } - }; - ServletConfig config = mock(ServletConfig.class); -+ when(config.getInitParameter(WebStorageEndPoint.STORAGE_CLASS)).thenReturn("fooKlazz"); // let it fail through -+ when(config.getInitParameter(WebStorageEndPoint.STORAGE_ENDPOINT)).thenReturn("fooEndPoint"); -+ - ThCreatorResult result = creatWorkingThermostatHome(); - System.setProperty(TH_HOME_PROP_NAME, result.thermostatHome.toFile().getAbsolutePath()); - endpoint.init(config); -@@ -248,7 +284,7 @@ - @Test - public void testShutDownCancelsTimers() { - TimerRegistry registry = mock(TimerRegistry.class); -- WebStorageEndPoint endpoint = new WebStorageEndPoint(registry, null, null); -+ WebStorageEndPoint endpoint = new WebStorageEndPoint(registry, null, null, storageFactoryProvider); - endpoint.destroy(); - verify(registry).shutDown(); - } -@@ -263,12 +299,57 @@ - ConfigurationFinder finder = mock(ConfigurationFinder.class); - when(finder.getConfiguration("web.auth")).thenReturn(null); - -- WebStorageEndPoint endpoint = new WebStorageEndPoint(registry, paths, finder); -+ WebStorageEndPoint endpoint = new WebStorageEndPoint(registry, paths, finder, storageFactoryProvider); - StorageCredentials creds = endpoint.getStorageCredentials(); - - assertNull(creds); - } -- -+ -+ @Test -+// @Bug(id = "PR2941", -+// url = "http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=2941", -+// summary = "Concurrent webstorage connections may cause storage exceptions") -+ @PrepareForTest({ WebStorageEndPoint.class }) -+ public void testStorageIsCreatedOnceOnInit() throws Exception { -+ final ServletContext mockContext = mock(ServletContext.class); -+ when(mockContext.getServerInfo()).thenReturn("jetty/9.1.0.v20131115"); -+ final ConfigurationFinder finder = mock(ConfigurationFinder.class); -+ when(finder.getConfiguration(anyString())).thenReturn(mock(File.class)); -+ FileBasedStorageCredentials mockCreds = mock(FileBasedStorageCredentials.class); -+ when(mockCreds.getUsername()).thenReturn("username"); -+ when(mockCreds.getPassword()).thenReturn("password".toCharArray()); -+ whenNew(FileBasedStorageCredentials.class).withAnyArguments().thenReturn(mockCreds); -+ @SuppressWarnings("serial") -+ WebStorageEndPoint endpoint = new WebStorageEndPoint(null, null, finder, storageFactoryProvider) { -+ @Override -+ public ServletContext getServletContext() { -+ return mockContext; -+ } -+ }; -+ ServletConfig config = mock(ServletConfig.class); -+ when(config.getInitParameter(WebStorageEndPoint.STORAGE_CLASS)).thenReturn("fooKlazz"); // let it fail through -+ when(config.getInitParameter(WebStorageEndPoint.STORAGE_ENDPOINT)).thenReturn("fooEndPoint"); -+ -+ ThCreatorResult result = creatWorkingThermostatHome(); -+ System.setProperty(TH_HOME_PROP_NAME, result.thermostatHome.toFile().getAbsolutePath()); -+ -+ // not created yet -+ verifyZeroInteractions(storageFactoryProvider); -+ verifyZeroInteractions(storageFactory); -+ -+ endpoint.init(mock(ServletConfig.class)); -+ -+ // created once -+ verify(storageFactoryProvider).createStorageFactory(); -+ verify(storageFactory).getStorage(anyString(), anyString(), any(CommonPaths.class), any(StorageCredentials.class)); -+ -+ endpoint.init(mock(ServletConfig.class)); -+ -+ // still only once -+ verify(storageFactoryProvider).createStorageFactory(); -+ verify(storageFactory).getStorage(anyString(), anyString(), any(CommonPaths.class), any(StorageCredentials.class)); -+ } -+ - private ThCreatorResult creatWorkingThermostatHome() throws IOException { - Path testThermostatHome = Files.createTempDirectory( - "foo-thermostat-home-", new FileAttribute[] {}); -diff --git a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java ---- a/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java -+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/WebStorageEndpointTest.java -@@ -223,7 +223,7 @@ - public void setUp() throws Exception { - - mockStorage = mock(BackingStorage.class); -- StorageFactory.setStorage(mockStorage); -+ StorageFactoryImpl.setStorage(mockStorage); - } - - private void startServer(int port, LoginService loginService) throws Exception { diff --git a/SOURCES/0004_verify_token_removal_fix.patch b/SOURCES/0004_verify_token_removal_fix.patch deleted file mode 100644 index f389dec..0000000 --- a/SOURCES/0004_verify_token_removal_fix.patch +++ /dev/null @@ -1,61 +0,0 @@ -# HG changeset patch -# User Jie Kang -# Date 1477487506 14400 -# Wed Oct 26 09:11:46 2016 -0400 -# Node ID a26429779377d98fcf07664d767f9d3400043eed -# Parent 9fe2266b4fa55eb78d372e634790878f328303f5 -Fix verified-token removal in TokenManager - -PR3210 - -Reviewed-by: jerboaa -Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-October/021425.html - -diff --git a/web/server/src/main/java/com/redhat/thermostat/web/server/TokenManager.java b/web/server/src/main/java/com/redhat/thermostat/web/server/TokenManager.java ---- a/web/server/src/main/java/com/redhat/thermostat/web/server/TokenManager.java -+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/TokenManager.java -@@ -85,12 +85,12 @@ - return token; - } - -- private void scheduleRemoval(final String clientToken) { -+ private void scheduleRemoval(final String clientKey) { - TimerTask task = new TimerTask() { - - @Override - public void run() { -- tokens.remove(clientToken); -+ tokens.remove(clientKey); - } - }; - timer.schedule(task, timeout); -@@ -111,7 +111,7 @@ - byte[] storedToken = tokens.get(clientKey); - boolean verified = Arrays.equals(candidateToken, storedToken); - if (verified) { -- tokens.remove(clientToken); -+ tokens.remove(clientKey); - } - return verified; - } -diff --git a/web/server/src/test/java/com/redhat/thermostat/web/server/TokenManagerTest.java b/web/server/src/test/java/com/redhat/thermostat/web/server/TokenManagerTest.java ---- a/web/server/src/test/java/com/redhat/thermostat/web/server/TokenManagerTest.java -+++ b/web/server/src/test/java/com/redhat/thermostat/web/server/TokenManagerTest.java -@@ -91,6 +91,17 @@ - } - - @Test -+ public void generateTokenCanNotBeReusedTest() { -+ TokenManager tokenManager = new TokenManager(mock(TimerRegistry.class)); -+ String clientToken = "something"; -+ String action = "myAction"; -+ byte[] token = tokenManager.generateToken(clientToken.getBytes(), action); -+ assertTrue(tokenManager.verifyToken(clientToken.getBytes(), token, action)); -+ // try again with same action name, which should not verify -+ assertFalse(tokenManager.verifyToken(clientToken.getBytes(), token, action)); -+ } -+ -+ @Test - public void generateAndVerifyTokenTest() { - TokenManager tokenManager = new TokenManager(mock(TimerRegistry.class)); - String clientToken = "something"; diff --git a/SOURCES/fedora-thermostatrc b/SOURCES/fedora-thermostatrc index d5072cb..a5cd2a3 100644 --- a/SOURCES/fedora-thermostatrc +++ b/SOURCES/fedora-thermostatrc @@ -44,10 +44,15 @@ ##################################################################### # +# Use a different JDK for running thermostat +# +#JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk + +# # Extra jar files which need to be on the classpath when # Thermostat boots. # -THERMOSTAT_EXT_BOOT_CLASSPATH="__TOOLS_PATH__" +THERMOSTAT_EXT_BOOT_CLASSPATH="${JAVA_HOME}/lib/tools.jar" THERMOSTAT_EXT_BOOT_CLASSPATH="${THERMOSTAT_EXT_BOOT_CLASSPATH}:${THERMOSTAT_HOME}/plugins/embedded-web-endpoint/jetty-schemas-3.1.M0.jar" export THERMOSTAT_EXT_BOOT_CLASSPATH diff --git a/SPECS/thermostat.spec b/SPECS/thermostat.spec index d5b6c8f..0ae001f 100644 --- a/SPECS/thermostat.spec +++ b/SPECS/thermostat.spec @@ -4,11 +4,11 @@ # Upstream Thermostat version triplet %global major 1 %global minor 6 -%global patchlevel 4 +%global patchlevel 6 # non_bootstrap_build == 1 means add self-BR so that # xmvn-subst symlinks correctly -%global non_bootstrap_build 1 +%global non_bootstrap_build 1 %if 0%{?rhel} @@ -43,18 +43,18 @@ ######################################### # Real OSGi Bundle-Version is 3.9.3.Final - %global netty_bundle_version 3.9.3 + %global netty_bundle_version 3.10.6 %global jcommon_bundle_version 1.0.23 %global jfreechart_bundle_version 1.0.19 # apache-commons-beanutils - %global beanutils_bundle_version 1.9.2 + %global beanutils_bundle_version 1.9.3 # apache-commons-codec %global codec_bundle_version 1.10.0 # apache-commons-collections %global collections_bundle_version 3.2.2 # apache-commons-logging %global logging_bundle_version 1.2.0 - %global hc_core_bundle_version 4.4.5 + %global hc_core_bundle_version 4.4.6 %global hc_client_bundle_version 4.5.2 %global gson_bundle_version 2.3.1 %global mongo_bundle_version 3.2.1 @@ -64,8 +64,12 @@ # endpoint plugin: a.k.a web-storage-service %global javax_servlet_bundle_version 3.1.0 %global javax_servlet_bsn javax.servlet-api - %global jgraphx_bundle_version 3.5.0 - %global jetty_version 9.4.0 + %global jgraphx_bundle_version 3.6.0 + # xmvn-subst in rawhide and later fedoras support + # in reactor symlinking. See RHBZ#1226251 + %global xmvn_subst_args -R %{buildroot} . + %global jetty_version 9.4.0.M0 + %global tomcat_version 8 %else @@ -86,7 +90,7 @@ %global logging_bundle_version 1.1.2 %global hc_core_bundle_version 4.3.3 %global hc_client_bundle_version 4.3.6 - %global gson_bundle_version 2.2.2 + %global gson_bundle_version 2.2.4 %global mongo_bundle_version 3.2.1 %global lucene_analysis_core_bsn org.apache.lucene.analyzers-common %global lucene_version 5.4.1 @@ -97,8 +101,9 @@ %global javax_servlet_bundle_version 3.0.0 %global javax_servlet_bsn javax.servlet %global jgraphx_bundle_version 3.1.2 + %global xmvn_subst_args . %global jetty_version 9.0.3.v20130506 - + %global tomcat_version 7 %endif # Jansi is used as bootstrap bundle and the @@ -108,15 +113,7 @@ # Base path to the JDK which will be used in boot scripts -%if 0%{?fedora} >= 22 - %global jdk_base /etc/alternatives/java_sdk_openjdk -%else - %if 0%{?is_rhel_6} - %global jdk_base /usr/lib/jvm/java-1.7.0-openjdk.x86_64 - %else - %global jdk_base /usr/lib/jvm/java-1.7.0-openjdk - %endif -%endif +%global jdk_base /usr/lib/jvm/java %{?scl:%scl_package thermostat} %{!?scl:%global pkg_name %{name}} @@ -134,6 +131,7 @@ %global system_logdir %{_root_localstatedir}/log/%{name} %global system_statedir %{_root_localstatedir}/run/%{name} %global system_sbindir %{_root_sbindir} + %global thermostat_desktop_app_anme "'Thermostat (from SCL)'" # directories for system user for storage as a systemd service %global user_datadir %{_localstatedir}/lib @@ -153,6 +151,7 @@ %global system_cachedir %{_localstatedir}/cache/%{name} %global system_logdir %{_localstatedir}/log/%{name} %global system_statedir %{_localstatedir}/run/%{name} + %global thermostat_desktop_app_anme Thermostat } # Some Maven coordinates mismatch due to compat versioning. @@ -221,7 +220,7 @@ Name: %{?scl_prefix}thermostat Version: %{major}.%{minor}.%{patchlevel} # If building from snapshot out of hg, uncomment and adjust below value as appropriate #Release: 0.1.20131122hg%{hgrev}%{?dist} -Release: %{custom_release}.5%{?dist} +Release: %{custom_release}.2%{?dist} Summary: A monitoring and serviceability tool for OpenJDK License: GPLv2+ with exceptions and OFL URL: http://icedtea.classpath.org/thermostat/ @@ -252,24 +251,12 @@ Patch1: 0001_shared_fix_bundle_loading.patch # is 4.3 OSGi spec. Patch2: 0002_shared_osgi_spec_fixes.patch -# This patch is in upstream and should be removed once the thermostat package -# in the collection is updated to the latest release. The changeset can be -# found at: -# http://icedtea.classpath.org/hg/release/thermostat-1.6/rev/7a1c62f9337b -# This resolves RHBZ#1329003 -Patch3: 0003_storage_init_fix.patch - -# This patch is in upstream and should be removed once the thermostat package -# in the collection is updated to the latest release. The changeset can be -# found at: -# http://icedtea.classpath.org/hg/release/thermostat-1.6/rev/a26429779377 -# This resolves RHBZ#1388898 -Patch4: 0004_verify_token_removal_fix.patch - +%{?scl: %if 0%{?non_bootstrap_build} # Work-around xmvn-subst limitation BuildRequires: %{?scl_prefix}thermostat-webapp = %{version} %endif +} # RHEL 6 does not have virtual provides java-devel >= 1.7 %if 0%{?is_rhel_6} @@ -313,7 +300,7 @@ BuildRequires: gtk2-devel BuildRequires: %{?scl_prefix_java_common}mvn(org.apache.felix:org.apache.felix.framework) BuildRequires: %{?scl_prefix_maven}mvn(org.fusesource:fusesource-pom:pom:) BuildRequires: %{?scl_prefix_java_common}mvn(org.apache.commons:commons-cli) -BuildRequires: %{?scl_prefix}mvn(jline:jline) +BuildRequires: %{?scl_prefix_java_common}mvn(jline:jline) BuildRequires: %{?scl_prefix_java_common}mvn(org.fusesource.jansi:jansi) BuildRequires: %{?scl_prefix_java_common}mvn(%{lucene_core_coords}) BuildRequires: %{?scl_prefix_java_common}mvn(%{lucene_analyzers_coords}) @@ -377,7 +364,7 @@ Requires: java-devel >= 1:1.8.0 } %{?scl: Requires: %{?scl_prefix}runtime -Requires: java-1.7.0-openjdk-devel +Requires: java-devel-openjdk >= 1:1.7 } # Only require mongodb-server on arches where it's available %ifarch %{arm} %{ix86} x86_64 @@ -423,6 +410,10 @@ Requires: %{?scl_prefix_java_common}osgi(org.apache.httpcomponents.httpcore) >= Requires: %{?scl_prefix_java_common}osgi(org.apache.httpcomponents.httpclient) >= %{hc_client_bundle_version} Requires: %{?scl_prefix_java_common}osgi(org.apache.httpcomponents.httpmime) >= %{hc_client_bundle_version} +# The version of asm that this package builds against gets bundled in +# See https://fedorahosted.org/fpc/ticket/226 for the same issue in another package +Provides: %{?scl_prefix}bundled(%{?scl_prefix_maven}mvn(%{object_web_asm_maven_coords}) + %description Thermostat is a monitoring and instrumentation tool for the Hotspot JVM, with support for monitoring multiple JVM instances. The system is made @@ -470,8 +461,9 @@ security. #%%setup -q -n %%{pkg_name}-%%{major}-%%{minor}-%%{hgrev} %patch1 -p1 %patch2 -p1 -%patch3 -p1 -%patch4 -p1 + +# Replace thermostatrc with Fedora's version +cp %{SOURCE4} distribution/config/thermostatrc # Fix up artifact names which have different name upstream # lucene @@ -485,9 +477,9 @@ security. %pom_add_dep "org.apache.lucene:lucene-core:%{lucene_version}" vm-heap-analysis/distribution # Fix up artifact names for jgraphx %pom_remove_dep "org.tinyjee.jgraphx:jgraphx" -%pom_add_dep "com.mxgraph:jgraphx:3.1.2.0" +%pom_add_dep "com.mxgraph:jgraphx:%{jgraphx_bundle_version}.0" %pom_remove_dep "org.tinyjee.jgraphx:jgraphx" thread/client-swing -%pom_add_dep "com.mxgraph:jgraphx:3.1.2.0" thread/client-swing +%pom_add_dep "com.mxgraph:jgraphx:%{jgraphx_bundle_version}.0" thread/client-swing # httpclient %pom_remove_dep org.apache.httpcomponents:httpclient-osgi web/client %pom_add_dep org.apache.httpcomponents:httpclient:4.4.0 web/client @@ -649,9 +641,13 @@ popd # install javadoc:aggregate # Everything after '--' is passed to plain xmvn/mvn %mvn_build -f -- -Dthermostat.home=%{thermostat_home} \ + -Dthermostat.jdk.home=%{jdk_base} \ -Dthermostat.system.user=thermostat \ -Dthermostat.system.group=thermostat \ + -Dthermostat.desktop.app.name=%{thermostat_desktop_app_name} \ -Dnetty.version=%{netty_bundle_version}.Final \ + -Dtomcat=%{tomcat_version} \ + -Dpkg_name=%{pkg_name} \ -Dcommons-logging.version=%{logging_bundle_version} \ -Dcommons-collections.version=%{collections_bundle_version} \ -Dcommons-codec.osgi-version=%{codec_bundle_version} \ @@ -669,22 +665,10 @@ popd -Dlucene.osgi-version=%{lucene_version} \ -Dosgi.compendium.bundle.symbolic-name=org.osgi.compendium \ -Dosgi.compendium.osgi-version=4.1.0 \ - -Djgraphx.osgi.version=%{jgraphx_bundle_version} \ + -Djgraphx.osgi.version=%{jgraphx_bundle_version}.0 \ -Djetty.javax.servlet.osgi.version=%{javax_servlet_bundle_version} \ -Djavax.servlet.bsn=%{javax_servlet_bsn} \ -Djetty.version=%{jetty_version} - -# Make path to java so that it keeps working after updates. -# We require java >= 1.7.0 -sed -i 's|^JAVA=.*|JAVA="%{jdk_base}/bin/java"|' distribution/target/image/bin/thermostat-agent-proxy -sed -i 's|^JAVA=.*|JAVA="%{jdk_base}/bin/java"|' distribution/target/image/bin/thermostat-webservice -sed -i 's|^JAVA=.*|JAVA="%{jdk_base}/bin/java"|' distribution/target/image/bin/thermostat-command-channel -sed -i 's|^JAVA=.*|JAVA="%{jdk_base}/bin/java"|' distribution/target/image/bin/thermostat -# Fix path to tools.jar, replace system thermostatrc -sed 's|__TOOLS_PATH__|%{jdk_base}/lib/tools.jar|' %{SOURCE4} > distribution/target/image/etc/thermostatrc -sed -i 's|^TOOLS_JAR=.*|TOOLS_JAR="%{jdk_base}/lib/tools.jar"|' distribution/target/image/bin/thermostat-agent-proxy -sed -i 's|^TOOLS_JAR=.*|TOOLS_JAR="%{jdk_base}/lib/tools.jar"|' distribution/target/image/bin/thermostat-command-channel -sed -i 's|^TOOLS_JAR=.*|TOOLS_JAR="%{jdk_base}/lib/tools.jar"|' distribution/target/image/bin/thermostat %{?scl:EOF} @@ -707,9 +691,11 @@ mkdir -p %{buildroot}%{system_initrddir} mkdir -p %{buildroot}%{_unitdir} %endif # Thermostat icon lives there -mkdir -p %{buildroot}%{_datarootdir}/icons/hicolor/scalable/apps +mkdir -p %{buildroot}%{system_root_datadir}/icons/hicolor/scalable/apps # Thermostat desktop lives there -mkdir -p %{buildroot}%{_datarootdir}/applications +mkdir -p %{buildroot}%{system_root_datadir}/applications +# Thermostat app data file lives there +mkdir -p %{buildroot}%{system_root_datadir}/appdata # Example config files are in docdir mkdir -p %{buildroot}%{_docdir}/%{pkg_name} # Man page @@ -779,6 +765,11 @@ install -m 0644 distribution/packaging/shared/man/%{pkg_name}.1 %{buildroot}%{_m mkdir -p %{buildroot}%{system_root_datadir}/bash-completion/completions install -pm 644 distribution/target/packaging/bash-completion/thermostat-completion %{buildroot}%{system_root_datadir}/bash-completion/completions/%{name} +# install files needed for proper desktop integration +install -m 0644 distribution/target/packaging/desktop/%{pkg_name}.desktop %{buildroot}%{system_root_datadir}/applications/%{name}.desktop +install -m 0644 distribution/target/packaging/icons/256px.svg %{buildroot}%{system_root_datadir}/icons/hicolor/scalable/apps/%{name}.svg +install -m 0644 distribution/target/packaging/desktop/%{pkg_name}.appdata.xml %{buildroot}%{system_root_datadir}/appdata/%{name}.appdata.xml + rm -rf distribution/target/image/bin/%{pkg_name}.orig # Remove developer setup things. rm distribution/target/image/bin/thermostat-devsetup @@ -791,13 +782,13 @@ cp -a distribution/target/image %{buildroot}%{thermostat_home} # Replace jars with symlinks to installed libs pushd %{buildroot}%{thermostat_home}/libs - xmvn-subst . + xmvn-subst %{xmvn_subst_args} popd # Do the same for thermostat plugin dirs pushd %{buildroot}%{thermostat_home}/plugins for plugin_name in $(ls); do pushd $plugin_name - xmvn-subst . + xmvn-subst %{xmvn_subst_args} popd done popd @@ -826,6 +817,8 @@ ln -s %{_datarootdir}/%{pkg_name}/bin/thermostat \ %{buildroot}%{_bindir}/thermostat ln -s %{_datarootdir}/%{pkg_name}/bin/thermostat-setup \ %{buildroot}%{_bindir}/thermostat-setup +ln -s %{_datarootdir}/%{pkg_name}/bin/thermostat-common \ + %{buildroot}%{_bindir}/thermostat-common # Move config files to /etc and symlink stuff under # $THERMOSTAT_HOME/etc to it. Put example configs @@ -890,7 +883,7 @@ ln -s %{thermostat_catalina_base}/webapps/%{pkg_name} %{buildroot}%{thermostat_h # Replace jars with symlinks pushd %{buildroot}%{thermostat_catalina_base}/webapps/%{pkg_name}/WEB-INF/lib - xmvn-subst . + xmvn-subst %{xmvn_subst_args} popd # Remove tools.jar (coming from the JVM). We also don't need jzlib.jars. @@ -964,15 +957,10 @@ mkdir -p %{buildroot}/%{_root_localstatedir}/log/%{thermostat_tomcat_service_nam %{?scl:EOF} %check -# Perform some sanity checks on paths to JAVA/TOOLS_JAR -# in important boot scripts. See RHBZ#1052992 and -# RHBZ#1053123 -TOOLS_JAR="$(grep -E THERMOSTAT_EXT_BOOT_CLASSPATH='.*tools.jar' %{buildroot}/%{_sysconfdir}/%{pkg_name}/thermostatrc | cut -d= -f2 | cut -d\" -f2)" -test "${TOOLS_JAR}" = "%{jdk_base}/lib/tools.jar" -TOOLS_JAR="$(grep 'TOOLS_JAR=' %{buildroot}/%{thermostat_home}/bin/thermostat-agent-proxy | cut -d= -f2 | cut -d\" -f2)" -test "${TOOLS_JAR}" = "%{jdk_base}/lib/tools.jar" -JAVA="$(grep 'JAVA=' %{buildroot}/%{thermostat_home}/bin/thermostat | cut -d= -f2 | cut -d\" -f2)" -test "${JAVA}" = "%{jdk_base}/bin/java" +# Perform a sanity check so as to ensure that JAVA_HOME will point to a +# stable path (across OpenJDK package updates). +JDK_HOME_CANDIDATE=$(grep 'jdk_home_candidate=' %{buildroot}/%{thermostat_home}/bin/thermostat-common | cut -d= -f2 | cut -d\" -f2) +test "${JDK_HOME_CANDIDATE}" = "%{jdk_base}" %pre %{?scl: @@ -990,7 +978,7 @@ ${__bin_dir}/useradd -c "Thermostat system user" -g thermostat \ # Install but don't activate %systemd_post %{?scl_prefix}%{pkg_name}-storage.service # Required for icon cache (i.e. Thermostat icon) -/bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || : +/bin/touch --no-create %{system_root_datadir}/icons/hicolor &>/dev/null || : %post webapp # install but don't activate @@ -1004,14 +992,14 @@ ${__bin_dir}/useradd -c "Thermostat system user" -g thermostat \ %postun # Required for icon cache (i.e. Thermostat icon) if [ $1 -eq 0 ] ; then - /bin/touch --no-create %{_datadir}/icons/hicolor &> /dev/null - /usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : + /bin/touch --no-create %{system_root_datadir}/icons/hicolor &> /dev/null + /usr/bin/gtk-update-icon-cache %{system_root_datadir}/icons/hicolor &>/dev/null || : fi %systemd_postun %{?scl_prefix}%{pkg_name}-storage.service %posttrans # Required for icon cache (i.e. Thermostat icon) -/usr/bin/gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : +/usr/bin/gtk-update-icon-cache %{system_root_datadir}/icons/hicolor &>/dev/null || : %files -f .mfiles %doc LICENSE @@ -1040,6 +1028,10 @@ fi # be installed %dir %{system_root_datadir}/bash-completion/completions %dir %{system_root_datadir}/bash-completion +# Own desktop related files +%{system_root_datadir}/applications/%{name}.desktop +%{system_root_datadir}/icons/hicolor/scalable/apps/%{name}.svg +%{system_root_datadir}/appdata/%{name}.appdata.xml %config(noreplace) %{_sysconfdir}/%{pkg_name}/plugins.d %config(noreplace) %{_sysconfdir}/%{pkg_name}/ssl.properties %config %{_sysconfdir}/%{pkg_name}/commands @@ -1089,6 +1081,7 @@ fi %{_jnidir}/thermostat-*.jar %{_bindir}/thermostat %{_bindir}/thermostat-setup +%{_bindir}/thermostat-common %dir %{_mandir}/man1 %{_mandir}/man1/%{pkg_name}.1* %if 0%{?with_systemd} @@ -1158,6 +1151,27 @@ fi %{_datadir}/%{pkg_name}/plugins/embedded-web-endpoint %changelog +* Wed Jan 18 2017 Jie Kang - 1.6.6-2 +- Add self-br for proper symlinking + +* Tue Jan 17 2017 Jie Kang - 1.6.6-1 +- Rebase to latest Thermostat 1.6.6. Resolves rhbz#1398394 + +* Mon Jan 16 2017 Jie Kang - 1.6.4-9 +- Use java-devel-openjdk requires instead of a hard + require on java-1.7.0-openjdk. Resolves rhbz#1398232 + +* Fri Jan 13 2017 Jie Kang - 1.6.4-8 +- Use /usr/lib/jvm/java for jdk base, a link managed by + alternatives. Resolves rhbz#1398252 + +* Wed Jan 11 2017 Jie Kang - 1.6.4-7 +- Use jline from rh-java-common instead of rh-thermostat16 +- Remove self-br for bootstrap build + +* Wed Jan 11 2017 Jie Kang - 1.6.4-6 +- Update version of gson package + * Wed Oct 26 2016 Jie Kang - 1.6.4-5 - Add patch for fixing verified token removal Resolves RHBZ#1388898