/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.configuration.internal; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.JsonLocation; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.JsonMappingException; import org.easymock.EasyMock; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import de.rcenvironment.core.configuration.ConfigurationService; import de.rcenvironment.core.configuration.ConfigurationServiceMessage; import de.rcenvironment.core.configuration.ConfigurationServiceMessageEvent; import de.rcenvironment.core.configuration.ConfigurationServiceMessageEventListener; import de.rcenvironment.core.configuration.bootstrap.BootstrapConfiguration; import de.rcenvironment.core.configuration.discovery.bootstrap.DiscoveryBootstrapService; import de.rcenvironment.core.configuration.discovery.bootstrap.DiscoveryConfiguration; import de.rcenvironment.core.utils.common.TempFileService; import de.rcenvironment.core.utils.common.TempFileServiceAccess; /** * TestCases for JSONConfigurationServiceTest.java. * * @author Heinrich Wendel * @author Tobias Menden * @author Robert Mischke */ public class ConfigurationServiceImplTest { private static final String DEFAULT_INSTANCE_DATA_SUBDIR_IN_USER_HOME = ".rce"; /** Name of the test configuration file. */ private static final String ID = "de.rcenvironment.rce.configuration.test"; /** Content of the test configuration file. */ private static final String CONTENT = "{\n \"booleanValue\": true\n}"; /** Default string value. */ private static final String STRING = "123"; private static final String BUNDLE_SYMBOLIC_NAME = "de.rcenvironment.core.configuration"; private static final String USER_HOME_SYSTEM_PROPERTY = "user.home"; private TempFileService tempFileService; private File mockInstallationDir; private File mockUserHome; private String originalUserHome; private final Log log = LogFactory.getLog(getClass()); private boolean bootstrapSettingsInitialized; private File mockInstallationConfigurationDir; /** * Common test setup. * * @throws IOException on setup errors */ @Before public void setup() throws IOException { TempFileServiceAccess.setupUnitTestEnvironment(); tempFileService = TempFileServiceAccess.getInstance(); mockInstallationDir = createTempDir("mockInstallationDir"); mockInstallationConfigurationDir = new File(mockInstallationDir, ConfigurationServiceImpl.CONFIGURATION_SUBDIRECTORY_PATH); originalUserHome = System.getProperty(USER_HOME_SYSTEM_PROPERTY); mockUserHome = createTempDir("mockUserHome"); System.setProperty(USER_HOME_SYSTEM_PROPERTY, mockUserHome.getAbsolutePath()); bootstrapSettingsInitialized = false; } /** * Common test tear-down. */ @After public void tearDown() { // restore user home System.setProperty(USER_HOME_SYSTEM_PROPERTY, originalUserHome); // delete override properties System.clearProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE); System.clearProperty(BootstrapConfiguration.SYSTEM_PROPERTY_DEFAULT_PROFILE_ID_OR_PATH); } /** * Test default behavior, configuration in instance home. */ @Test public void testInstance() { createMockConfigurationFile(mockInstallationDir, ID, CONTENT); ConfigurationServiceImpl service = createDefaultTestInstance(); DummyConfiguration configuration = service.getConfiguration(ID, DummyConfiguration.class); assertEquals(configuration.isBooleanValue(), true); assertEquals(configuration.getStringValue(), STRING); } /** * Test default behavior, configuration in user home. */ @Test @Ignore // TODO review test semantics; is configuration read from user home anymore? - misc_ro public void testUserHome() { File tempDir = createTempDir(); // TODO replace with INSTANCE_HOME_PARENT_DIR_OVERRIDE_SYSTEM_PROPERTY? System.setProperty(USER_HOME_SYSTEM_PROPERTY, tempDir.getAbsolutePath()); File rceDir = new File(tempDir.getAbsoluteFile() + File.separator + DEFAULT_INSTANCE_DATA_SUBDIR_IN_USER_HOME + File.separator + ConfigurationServiceImpl.CONFIGURATION_SUBDIRECTORY_PATH); rceDir.mkdirs(); createMockConfigurationFile(rceDir, ID, CONTENT); ConfigurationServiceImpl service = createDefaultTestInstance(); DummyConfiguration configuration = service.getConfiguration(ID, DummyConfiguration.class); assertEquals(configuration.isBooleanValue(), true); assertEquals(configuration.getStringValue(), STRING); removeTempDir(tempDir); } /** * Tests "instance data directory" default value. */ @Test public void testProfileDirDefault() { ConfigurationServiceImpl service = createDefaultTestInstance(); String dataDirPath = service.getProfileDirectory().getAbsolutePath(); assertTrue(dataDirPath, dataDirPath.endsWith(File.separator + DEFAULT_INSTANCE_DATA_SUBDIR_IN_USER_HOME + File.separator + "default")); } /** * Tests "instance data directory" configuration with a relative path. */ @Test public void testProfileDirRelativePath() { System.setProperty(BootstrapConfiguration.SYSTEM_PROPERTY_DEFAULT_PROFILE_ID_OR_PATH, "myDataDir"); ConfigurationServiceImpl service = createDefaultTestInstance(); String dataDirPath = service.getProfileDirectory().getAbsolutePath(); assertTrue(dataDirPath, dataDirPath.endsWith(File.separator + DEFAULT_INSTANCE_DATA_SUBDIR_IN_USER_HOME + File.separator + "myDataDir")); } /** * Tests "instance data directory" configuration with a relative path. */ @Test public void testProfileDirAbsolutePath() { File tempDir = createTempDir(); System.setProperty(BootstrapConfiguration.SYSTEM_PROPERTY_DEFAULT_PROFILE_ID_OR_PATH, tempDir.getAbsolutePath()); ConfigurationServiceImpl service = createDefaultTestInstance(); String dataDirPath = service.getProfileDirectory().getAbsolutePath(); assertEquals(dataDirPath, tempDir.getAbsolutePath(), dataDirPath); } /** * Test configuration directory specified by property. */ @Test public void testProperty() { createMockConfigurationFile(mockInstallationDir, ID, CONTENT); ConfigurationServiceImpl service = createDefaultTestInstance(); DummyConfiguration configuration = service.getConfiguration(ID, DummyConfiguration.class); assertEquals(configuration.isBooleanValue(), true); assertEquals(configuration.getStringValue(), STRING); } /** * Test broken configuration, default should be provided. */ @Test public void testBroken() { File tempDir = createTempDir(); System.setProperty(ConfigurationService.SYSTEM_PROPERTY_OSGI_INSTALL_AREA, tempDir.getAbsolutePath() + File.separator); createMockConfigurationFile(tempDir, ID, "asdkf"); ConfigurationServiceImpl service = createDefaultTestInstance(); DummyConfiguration configuration = service.getConfiguration(ID, DummyConfiguration.class); assertEquals(configuration.isBooleanValue(), false); assertEquals(configuration.getStringValue(), STRING); removeTempDir(tempDir); } /** * Test missing configuration, default should be provided. */ @Test public void testMissing() { File tempDir = createTempDir(); System.setProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE, tempDir.getAbsolutePath()); ConfigurationServiceImpl service = createDefaultTestInstance(); DummyConfiguration configuration = service.getConfiguration(ID, DummyConfiguration.class); assertEquals(configuration.isBooleanValue(), false); assertEquals(configuration.getStringValue(), STRING); removeTempDir(tempDir); } /** Test. */ @Test @Ignore // TODO review test semantics - misc_ro, April 2014 public void testGetAbsolutePath() { ConfigurationServiceImpl service = createDefaultTestInstance(); File tempDir = createTempDir(); assertEquals(tempDir.getAbsolutePath(), service.resolveBundleConfigurationPath(ID, tempDir.getAbsolutePath())); System.setProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE, tempDir.getAbsolutePath()); service = createDefaultTestInstance(); assertEquals(tempDir.getAbsolutePath() + File.separator + ID + File.separator + tempDir.getName(), service.resolveBundleConfigurationPath(ID, tempDir.getName())); removeTempDir(tempDir); } /** Test the default instance name. */ @Test public void testGetInstanceNameForSuccess() { ConfigurationServiceImpl service = createDefaultTestInstance(); String expectedStart = "Unnamed instance started by "; // self-test: check pattern for expected start assertTrue(ConfigurationServiceImpl.DEFAULT_INSTANCE_NAME_VALUE.startsWith(expectedStart)); // the actual test assertTrue(service.getInstanceName().startsWith(expectedStart)); } /** Test. */ @Test public void testAddErrorListenerSuccess() { final ConfigurationServiceImpl service = createDefaultTestInstance(); final ConfigurationServiceMessageEventListener listener = EasyMock.createMock(ConfigurationServiceMessageEventListener.class); listener.handleConfigurationServiceError((ConfigurationServiceMessageEvent) EasyMock.anyObject()); EasyMock.replay(listener); service.addErrorListener(listener); final String messageContentString = "message"; service.fireErrorEvent(new ConfigurationServiceMessage(messageContentString)); EasyMock.verify(listener); } /** Test. */ @Test public void testAddErrorListenerFailure() { final ConfigurationServiceImpl service = createDefaultTestInstance(); try { service.addErrorListener(null); Assert.fail(); } catch (NullPointerException ok) { ok = null; } } /** Test. */ @Test public void testRemoveErrorListenerSuccess() { final ConfigurationServiceImpl service = createDefaultTestInstance(); final ConfigurationServiceMessageEventListener listener = EasyMock.createMock(ConfigurationServiceMessageEventListener.class); listener.handleConfigurationServiceError((ConfigurationServiceMessageEvent) EasyMock.anyObject()); EasyMock.expectLastCall().times(3); EasyMock.replay(listener); // register the listener and assert it is registered and the handler gets invoked multiple // times without being unregistered service.addErrorListener(listener); final String messageContentString = "message"; service.fireErrorEvent(new ConfigurationServiceMessage(messageContentString)); service.fireErrorEvent(new ConfigurationServiceMessage(messageContentString)); service.fireErrorEvent(new ConfigurationServiceMessage(messageContentString)); // remove the listener and make sure it does not get invoked any more service.removeErrorListener(listener); service.fireErrorEvent(new ConfigurationServiceMessage(messageContentString)); EasyMock.verify(listener); } /** Test. */ @Test public void testRemoveErrorListenerFailure() { final ConfigurationServiceImpl service = createDefaultTestInstance(); try { service.removeErrorListener(null); Assert.fail(); } catch (NullPointerException ok) { ok = null; } } /** * Test. * * @throws IOException on unexpected failure */ // TODO test does not fit the new mocking approach; rework? - misc_ro @Test public void testGetConfigurationParsingErrors() throws IOException { System.setProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE, mockInstallationDir.getAbsolutePath()); /** * {@link ConfigurationServiceMessageEventListener} implementation caching the most recent error message. */ class CachingListener implements ConfigurationServiceMessageEventListener { private String lastErrorMessage; @Override public void handleConfigurationServiceError(ConfigurationServiceMessageEvent error) { lastErrorMessage = error.getError().getMessage(); } } // create a dummy configuration file so the overwritten parsing method is actually called mockInstallationConfigurationDir.mkdir(); new File(mockInstallationConfigurationDir, "dummy.json").createNewFile(); // note: this must be activated after the mock configuration directory is created final CustomTestConfigurationService service = new CustomTestConfigurationService(); service.mockActivate(); final CachingListener listener = new CachingListener(); service.addErrorListener(listener); // test parsing error service.setExceptionToThrow(new JsonParseException("parsing error", null)); service.getConfiguration("dummy", Object.class); Assert.assertNotNull(listener.lastErrorMessage); Assert.assertTrue(!listener.lastErrorMessage.isEmpty()); // test mapping error service.setExceptionToThrow(new JsonMappingException("mapping error", (JsonLocation) null)); service.getConfiguration("dummy", Object.class); Assert.assertTrue(!listener.lastErrorMessage.isEmpty()); } /** Tests the behavior with no explicit installation path and an undefined OSGi install area. */ @Test public void testUnidentifiableInstallDataLocation() { //Initialize bootstrap configuration to avoid NPE in case it has not been initialized before. if (!bootstrapSettingsInitialized) { try { BootstrapConfiguration.initialize(); // apply simulated home directory } catch (IOException e) { throw new RuntimeException(e); // avoid IOException handling in many tests for a rare failure case } bootstrapSettingsInitialized = true; } System.clearProperty(ConfigurationService.SYSTEM_PROPERTY_OSGI_INSTALL_AREA); final ConfigurationServiceImpl service = new ConfigurationServiceImpl(); try { service.mockActivate(); fail("Exception expected"); } catch (IllegalStateException e) { assertTrue(e.getMessage().contains(ConfigurationService.SYSTEM_PROPERTY_OSGI_INSTALL_AREA)); } } /** Test. */ @Test public void testGetConfigurationFailure() { final ConfigurationServiceImpl service = createDefaultTestInstance(); /** Test. */ final class Test { private Test() { // do nothing } } try { log.debug("Expecting an InstantiationException on the next call; ignore related log output"); service.getConfiguration("test", Test.class); Assert.fail(); } catch (RuntimeException e) { if (!(e.getCause() instanceof InstantiationException)) { Assert.fail(); } } } /** * Test for addSubstitutionProperties() and getConfiguration(). * * @throws IOException on I/O errors */ @Test public void testGetPropertySubstitution() throws IOException { if (!ConfigurationServiceImpl.PROPERTY_SUBSTITUTION_MECHANISM_ENABLED) { log.debug("Compiled with property subsctitution mechanism disabled; skipping test"); return; } String configFileBasename = "temp.unittest"; String testNamespace = "testns"; File tempDir = TempFileServiceAccess.getInstance().createManagedTempDir(); System.setProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE, tempDir.getAbsolutePath()); File testConfigFile = new File(tempDir, configFileBasename + ".json"); if (testConfigFile.exists()) { Assert.fail("Unexpected state: File " + testConfigFile.getAbsolutePath() + " already exists"); } final ConfigurationServiceImpl service = new ConfigurationServiceImpl(); DummyConfiguration config; Map<String, String> testProperties; // check basic property file reading FileUtils.writeStringToFile(testConfigFile, "{ \"stringValue\": \"hardcoded\" }"); config = service.getConfiguration(configFileBasename, DummyConfiguration.class); Assert.assertEquals("No-substitution test failed", "hardcoded", config.getStringValue()); // test property file reading with missing property // test property file reading with defined property not containing quotes FileUtils.writeStringToFile(testConfigFile, "{ \"stringValue\": \"${testns:unquoted}\" }"); testProperties = new HashMap<String, String>(); testProperties.put("unquoted", "unquotedValue"); service.addSubstitutionProperties(testNamespace, testProperties); config = service.getConfiguration(configFileBasename, DummyConfiguration.class); Assert.assertEquals("Unquoted value test failed", "unquotedValue", config.getStringValue()); // test property file reading with defined property containing quotes FileUtils.writeStringToFile(testConfigFile, "{ \"stringValue\": ${testns:quoted} }"); testProperties = new HashMap<String, String>(); testProperties.put("quoted", "\"quotedValue\""); service.addSubstitutionProperties(testNamespace, testProperties); config = service.getConfiguration(configFileBasename, DummyConfiguration.class); Assert.assertEquals("Quoted value test failed", "quotedValue", config.getStringValue()); // test with boolean property FileUtils.writeStringToFile(testConfigFile, "{ \"booleanValue\": ${testns:boolKey} }"); testProperties = new HashMap<String, String>(); testProperties.put("boolKey", "true"); // class default is "false"; no quotes service.addSubstitutionProperties(testNamespace, testProperties); config = service.getConfiguration(configFileBasename, DummyConfiguration.class); Assert.assertEquals("Boolean value test failed", true, config.isBooleanValue()); } /** * Creates a new temporary directory. * * @return The File object of the directory. */ private File createTempDir() { try { return tempFileService.createManagedTempDir(); } catch (IOException e) { throw new IllegalStateException("Failed to create test temp dir", e); } } /** * Creates a new temporary directory. * * @return The File object of the directory. */ private File createTempDir(String infoText) { try { return tempFileService.createManagedTempDir(infoText); } catch (IOException e) { throw new IllegalStateException("Failed to create test temp dir", e); } } /** * Recursively removes a temporary directory. * * @param file The File object of the directory. */ private void removeTempDir(File file) { try { FileUtils.deleteDirectory(file); } catch (IOException e) { throw new RuntimeException(e); } } /** * Creates a test instance if {@link ConfigurationServiceImpl} with an injected mock discovery bootstrap service. In this method * variant, the mocked discovery service returns an empty map of discovery properties. * * @return the created instance */ public ConfigurationServiceImpl createDefaultTestInstance() { return createTestInstance(mockInstallationDir, new HashMap<String, String>()); } /** * Creates a test instance if {@link ConfigurationServiceImpl} with an injected mock discovery bootstrap service. In this method * variant, the mocked discovery service returns the provided map of discovery properties to the calling * {@link ConfigurationServiceImpl}. * * @param installationDir * @return the created instance */ private ConfigurationServiceImpl createTestInstance(File installationDir, Map<String, String> substitutionProperties) { if (!bootstrapSettingsInitialized) { try { BootstrapConfiguration.initialize(); // apply simulated home directory } catch (IOException e) { throw new RuntimeException(e); // avoid IOException handling in many tests for a rare failure case } bootstrapSettingsInitialized = true; } DiscoveryBootstrapService discoveryBootstrapService = EasyMock.createMock(DiscoveryBootstrapService.class); EasyMock.expect(discoveryBootstrapService.getSymbolicBundleName()).andReturn("de.rcenvironment.rce.configuration.discovery"); EasyMock.expect(discoveryBootstrapService.initializeDiscovery((DiscoveryConfiguration) EasyMock.anyObject())).andReturn( substitutionProperties); EasyMock.replay(discoveryBootstrapService); System.setProperty(ConfigurationService.SYSTEM_PROPERTY_INSTALLATION_DATA_ROOT_OVERRIDE, installationDir.getAbsolutePath()); ConfigurationServiceImpl service = new ConfigurationServiceImpl(); service.bindDiscoveryBootstrapService(discoveryBootstrapService); service.mockActivate(); return service; } /** * Creates a test configuration in the given directory. * * @param installationDataDir the mock installation directory * @param id The id of the test configuration. * @param content The content of the test configuration. */ private void createMockConfigurationFile(File installationDataDir, String id, String content) { File configDir = new File(installationDataDir, "configuration"); configDir.mkdir(); File file = new File(configDir, id + ".json"); try { file.createNewFile(); FileWriter fstream = new FileWriter(file); fstream.write(content); fstream.close(); } catch (IOException e) { throw new RuntimeException(e); } } /** * Creates a bundleContextMock. * * @return bundleContextMock. */ public BundleContext getBundleContextMock() { Bundle bundleMock = EasyMock.createNiceMock(Bundle.class); EasyMock.expect(bundleMock.getSymbolicName()).andReturn(BUNDLE_SYMBOLIC_NAME).anyTimes(); EasyMock.replay(bundleMock); BundleContext bundleContextMock = EasyMock.createNiceMock(BundleContext.class); EasyMock.expect(bundleContextMock.getBundle()).andReturn(bundleMock).anyTimes(); EasyMock.replay(bundleContextMock); return bundleContextMock; } /** * {@link ConfigurationServiceImpl that throws parsing exceptions. */ private class CustomTestConfigurationService extends ConfigurationServiceImpl { private Exception exceptionToThrow; private void setExceptionToThrow(final JsonParseException exceptionToThrow) { this.exceptionToThrow = exceptionToThrow; } private void setExceptionToThrow(final JsonMappingException exceptionToThrow) { this.exceptionToThrow = exceptionToThrow; } @Override protected <T> T parseConfigurationFile(Class<T> clazz, File file) throws IOException, JsonParseException, JsonMappingException { if (exceptionToThrow instanceof JsonParseException) { throw (JsonParseException) exceptionToThrow; } else if (exceptionToThrow instanceof JsonMappingException) { throw (JsonMappingException) exceptionToThrow; } else { throw new RuntimeException(exceptionToThrow); } } } }