blob: c444420d2ad6511ebd1a684d9d3b2348c2e9d0e8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.apache.felix.cm.MockBundleContext;
import org.apache.felix.cm.MockLogService;
import org.apache.felix.cm.MockNotCachablePersistenceManager;
import org.apache.felix.cm.MockPersistenceManager;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.helper.ManagedServiceFactoryTracker;
import org.apache.felix.cm.impl.persistence.CachingPersistenceManagerProxy;
import org.apache.felix.cm.impl.persistence.PersistenceManagerProxy;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.SynchronousConfigurationListener;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
public class ConfigurationManagerTest
{
private PrintStream replacedStdErr;
private ByteArrayOutputStream output;
@Before
public void setUp() throws Exception
{
replacedStdErr = System.err;
output = new ByteArrayOutputStream();
System.setErr( new PrintStream( output ) );
setLogLevel(LogService.LOG_WARNING);
}
@After
public void tearDown() throws Exception
{
System.setErr( replacedStdErr );
}
@Test public void test_listConfigurations_cached() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm =new MockPersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
ConfigurationImpl[] conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
dictionary = new Hashtable<>();
dictionary.put( "property1", "value2" );
pid = "testDefaultPersistenceManager";
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
// verify that the property in the configurations cache was used
assertEquals("value1", conf[0].getProperties(true).get("property1"));
}
@Test public void test_listConfigurations_notcached() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm = new MockNotCachablePersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(pm), null);
ConfigurationImpl[] conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
dictionary = new Hashtable<>();
dictionary.put("property1", "valueNotCached");
pid = "testDefaultPersistenceManager";
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
// verify that the value returned was not the one from the cache
assertEquals("valueNotCached", conf[0].getProperties(true).get("property1"));
}
@Test public void test_listConfigurations_notcached_handlesUpdates() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm = new MockNotCachablePersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(pm), null) {
@Override
void updated(ConfigurationImpl config, boolean fireEvent) {
}
};
ConfigurationImpl[] conf1 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf1.length);
assertEquals(2, conf1[0].getProperties(true).size());
// internal changecount
long revision = conf1[0].getRevision();
dictionary = new Hashtable<>();
dictionary.put("property1", "valueNotCached");
dictionary.put( Constants.SERVICE_PID, pid );
conf1[0].update(dictionary);
assertEquals(revision + 1, conf1[0].getRevision());
ConfigurationImpl[] conf2 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf2.length);
assertEquals(2, conf2[0].getProperties(true).size());
assertEquals(revision + 1, conf2[0].getRevision());
dictionary = new Hashtable<>();
dictionary.put("property1", "secondUpdate");
dictionary.put( Constants.SERVICE_PID, pid );
conf2[0].update(dictionary);
assertEquals(revision + 2, conf2[0].getRevision());
ConfigurationImpl[] conf3 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf3.length);
assertEquals(2, conf3[0].getProperties(true).size());
assertEquals(revision + 2, conf3[0].getRevision());
}
@Test public void testLogNoLogService() throws IOException
{
ConfigurationManager configMgr = createConfigurationManagerAndLog( null );
setLogLevel( LogService.LOG_WARNING );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// lower than error -- no output
setLogLevel( LogService.LOG_ERROR - 1 );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertNoLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// minimal log level -- no output
setLogLevel( Integer.MIN_VALUE );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertNoLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_INFO );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_DEBUG );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// maximal log level -- all output
setLogLevel( Integer.MAX_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
}
// this test always expects output since when using a LogService, the log
// level property is ignored
@Test public void testLogWithLogService() throws IOException
{
LogService logService = new MockLogService();
ConfigurationManager configMgr = createConfigurationManagerAndLog( logService );
setLogLevel( LogService.LOG_WARNING );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR - 1 );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( Integer.MIN_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_INFO );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_DEBUG );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( Integer.MAX_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
}
@Test public void testLogSetup() throws IOException
{
final MockBundleContext bundleContext = new MockBundleContext();
createConfigurationManagerAndLog( null );
// ensure the configuration data goes to target
bundleContext.setProperty( "felix.cm.dir", "target/config" );
// default value is 2
bundleContext.setProperty( "felix.cm.loglevel", null );
Log.logger.start( bundleContext );
assertEquals( 2, getLogLevel( ) );
Log.logger.stop( );
// illegal number yields default value
bundleContext.setProperty( "felix.cm.loglevel", "not-a-number" );
Log.logger.start( bundleContext );
assertEquals( 2, getLogLevel( ) );
Log.logger.stop( );
bundleContext.setProperty( "felix.cm.loglevel", "-100" );
Log.logger.start( bundleContext );
assertEquals( -100, getLogLevel( ) );
Log.logger.stop( );
bundleContext.setProperty( "felix.cm.loglevel", "4" );
Log.logger.start( bundleContext );
assertEquals( 4, getLogLevel( ) );
Log.logger.stop( );
}
@Test public void testEventsStartingBundle() throws Exception
{
final Set<String> result = new HashSet<>();
SynchronousConfigurationListener syncListener1 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L1");
}
};
SynchronousConfigurationListener syncListener2 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L2");
}
};
SynchronousConfigurationListener syncListener3 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L3");
}
};
ServiceReference mockRef = Mockito.mock( ServiceReference.class );
ServiceRegistration mockReg = Mockito.mock( ServiceRegistration.class );
Mockito.when( mockReg.getReference() ).thenReturn( mockRef );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(new MockPersistenceManager()), null);
setServiceTrackerField( configMgr, "configurationListenerTracker" );
ServiceReference[] refs =
setServiceTrackerField( configMgr, "syncConfigurationListenerTracker",
syncListener1, syncListener2, syncListener3 );
for ( int i=0; i < refs.length; i++)
{
Bundle mockBundle = Mockito.mock( Bundle.class );
switch (i)
{
case 0:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.ACTIVE );
break;
case 1:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.STARTING );
break;
case 2:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.STOPPING );
break;
}
Mockito.when( refs[i].getBundle() ).thenReturn( mockBundle );
}
Field srField = configMgr.getClass().getDeclaredField( "configurationAdminRegistration" );
srField.setAccessible( true );
srField.set( configMgr, mockReg );
Field utField = configMgr.getClass().getDeclaredField( "updateThread" );
utField.setAccessible( true );
utField.set( configMgr, new UpdateThread( null, "Test updater" ));
Dictionary<String, Object> props = new Hashtable<>();
props.put( Constants.SERVICE_PID, "org.acme.testpid" );
ConfigurationImpl config = new ConfigurationImpl( configMgr, new MockPersistenceManager(), props );
configMgr.updated( config, true );
assertEquals("Both listeners should have been called, both in the STARTING and ACTIVE state, but not in the STOPPING state",
2, result.size());
}
@Test
public void test_factoryConfigurationCleanup() throws Exception
{
MockNotCachablePersistenceManager pm = new MockNotCachablePersistenceManager();
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
final Field bcField = configMgr.getClass().getDeclaredField("bundleContext");
bcField.setAccessible(true);
bcField.set(configMgr, new MockBundleContext());
setServiceTrackerField( configMgr, "configurationListenerTracker" );
setServiceTrackerField( configMgr, "syncConfigurationListenerTracker" );
final Field mstField = configMgr.getClass().getDeclaredField("managedServiceFactoryTracker");
mstField.setAccessible(true);
mstField.set( configMgr, new ManagedServiceFactoryTracker(configMgr) {
@Override
public void open() {
}
});
final Field utField = configMgr.getClass().getDeclaredField( "updateThread" );
utField.setAccessible( true );
utField.set( configMgr, new UpdateThread( null, "Test updater" ) {
@Override
void schedule(Runnable update) {
update.run();
}
});
final String factoryPid = "my.factory";
final Dictionary<String, Object> props = new Hashtable<>();
props.put("hello", "world");
final ConfigurationImpl c1 = configMgr.createFactoryConfiguration(factoryPid, null);
c1.update(props);
final ConfigurationImpl c2 = configMgr.createFactoryConfiguration(factoryPid, null);
c2.update(props);
final ConfigurationImpl c3 = configMgr.createFactoryConfiguration(factoryPid, null);
c3.update(props);
assertEquals(3, pm.getStored().size());
c1.delete();
assertEquals(2, pm.getStored().size());
c2.delete();
assertEquals(1, pm.getStored().size());
c3.delete();
assertEquals(0, pm.getStored().size());
}
@Test
public void test_namedFactoryConfigurationCleanup() throws Exception {
MockNotCachablePersistenceManager pm = new MockNotCachablePersistenceManager();
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
final ConfigurationAdmin admin = new ConfigurationAdminImpl(configMgr, null);
final Field activeField = configMgr.getClass().getDeclaredField("isActive");
activeField.setAccessible(true);
activeField.set(configMgr, Boolean.TRUE);
setServiceTrackerField(configMgr, "configurationListenerTracker");
setServiceTrackerField(configMgr, "syncConfigurationListenerTracker");
final Field bcField = configMgr.getClass().getDeclaredField("bundleContext");
bcField.setAccessible(true);
bcField.set(configMgr, new MockBundleContext());
setServiceTrackerField(configMgr, "configurationListenerTracker");
setServiceTrackerField(configMgr, "syncConfigurationListenerTracker");
final Field mstField = configMgr.getClass().getDeclaredField("managedServiceFactoryTracker");
mstField.setAccessible(true);
mstField.set(configMgr, new ManagedServiceFactoryTracker(configMgr) {
@Override
public void open() {
}
});
final Field utField = configMgr.getClass().getDeclaredField("updateThread");
utField.setAccessible(true);
utField.set(configMgr, new UpdateThread(null, "Test updater") {
@Override
void schedule(Runnable update) {
update.run();
}
});
final String factoryPid = "my.factory";
final Dictionary<String, Object> props = new Hashtable<>();
props.put("hello", "world");
final Configuration c1 = admin.getFactoryConfiguration(factoryPid, "1", null);
c1.update(props);
final Configuration c2 = admin.getFactoryConfiguration(factoryPid, "2", null);
c2.update(props);
final Configuration c3 = admin.getFactoryConfiguration(factoryPid, "3", null);
c3.update(props);
assertEquals(3, pm.getStored().size());
c1.delete();
assertEquals(2, pm.getStored().size());
c2.delete();
assertEquals(1, pm.getStored().size());
c3.delete();
assertEquals(0, pm.getStored().size());
}
private void assertNoLog( ConfigurationManager configMgr, int level, String message, Throwable t )
{
try
{
Log.logger.log( level, message, t );
assertTrue( "Expecting no log output", output.size() == 0 );
}
finally
{
// clear the output for future data
output.reset();
}
}
private void assertLog( ConfigurationManager configMgr, int level, String message, Throwable t )
{
try
{
Log.logger.log( level, message, t );
assertTrue( "Expecting log output", output.size() > 0 );
final String expectedLog = MockLogService.toMessageLine( level, message );
final String actualLog = new String( output.toByteArray() );
assertEquals( "Log Message not correct", expectedLog, actualLog );
}
finally
{
// clear the output for future data
output.reset();
}
}
private static void setLogLevel( int level )
{
final String fieldName = "logLevel";
try
{
Field field = Log.class.getDeclaredField( fieldName );
field.setAccessible( true );
field.setInt( Log.logger, level );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot set logLevel field value" )
.initCause( ignore );
}
}
private static int getLogLevel( )
{
final String fieldName = "logLevel";
try
{
Field field = Log.class.getDeclaredField( fieldName );
field.setAccessible( true );
return field.getInt( Log.logger );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot get logLevel field value" )
.initCause( ignore );
}
}
private static ServiceReference[] setServiceTrackerField( ConfigurationManager configMgr,
String fieldName, Object ... services ) throws Exception
{
final Map<ServiceReference, Object> refMap = new HashMap<>();
for ( Object svc : services )
{
ServiceReference sref = Mockito.mock( ServiceReference.class );
Mockito.when( sref.getProperty( "objectClass" ) ).thenReturn(new String[] { "TestService" });
refMap.put( sref, svc );
}
Field field = configMgr.getClass().getDeclaredField( fieldName );
field.setAccessible( true );
field.set( configMgr, new ServiceTracker( new MockBundleContext(), "", null )
{
@Override
public ServiceReference[] getServiceReferences()
{
return refMap.keySet().toArray( new ServiceReference[0] );
}
@Override
public Object getService(ServiceReference reference)
{
return refMap.get( reference );
}
} );
return refMap.keySet().toArray(new ServiceReference[0]);
}
private static ConfigurationManager createConfigurationManagerAndLog( final LogService logService )
throws IOException
{
final PersistenceManager pm = Mockito.mock(PersistenceManager.class);
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
try
{
Field field = Log.class.getDeclaredField( "logTracker" );
field.setAccessible( true );
field.set( Log.logger, new ServiceTracker( new MockBundleContext(), "", null )
{
@Override
public Object getService()
{
return logService;
}
} );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot set logTracker field value" )
.initCause( ignore );
}
return configMgr;
}
}