blob: 271520c89ccf9a120ce523b07b7bcde5ea26668d [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.deployment.rp.autoconf;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Dictionary;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import junit.framework.TestCase;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.deploymentadmin.DeploymentPackage;
import org.osgi.service.deploymentadmin.spi.DeploymentSession;
import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
import org.osgi.service.log.LogService;
public class AutoConfResourceProcessorTest extends TestCase
{
private static class ConfigurationAdminImpl implements ConfigurationAdmin
{
private final String[] m_expectedPIDs;
private final String[] m_expectedFactoryPIDs;
private final Map<String, ConfigurationImpl> m_configs;
public ConfigurationAdminImpl(String... expectedPIDs)
{
this(expectedPIDs, new String[0]);
}
public ConfigurationAdminImpl(String[] expectedPIDs, String[] expectedFactoryPIDs)
{
m_expectedPIDs = expectedPIDs;
m_expectedFactoryPIDs = expectedFactoryPIDs;
m_configs = new LinkedHashMap<String, ConfigurationImpl>();
}
public Configuration createFactoryConfiguration(String factoryPid) throws IOException
{
return createFactoryConfiguration(factoryPid, null);
}
public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException
{
if (!isExpected(m_expectedFactoryPIDs, factoryPid))
{
throw new IOException("Unexpected factory PID: " + factoryPid);
}
// This should be unique enough for our use cases...
String pid = String.format("pid%d", m_configs.size());
ConfigurationImpl config = m_configs.get(pid);
if (config == null)
{
config = new ConfigurationImpl(factoryPid, pid, location);
m_configs.put(pid, config);
}
config.setBundleLocation(location);
return config;
}
public Configuration getConfiguration(String pid) throws IOException
{
return getConfiguration(pid, null);
}
public Configuration getConfiguration(String pid, String location) throws IOException
{
if (!isExpected(m_expectedPIDs, pid))
{
throw new IOException("Unexpected PID: " + pid);
}
ConfigurationImpl config = m_configs.get(pid);
if (config == null)
{
config = new ConfigurationImpl(null, pid, location);
m_configs.put(pid, config);
}
config.setBundleLocation(location);
return config;
}
public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException
{
return null;
}
private boolean isExpected(String[] expectedPIDs, String actualPID)
{
for (String expectedPID : expectedPIDs)
{
if (actualPID.equals(expectedPID))
{
return true;
}
}
return false;
}
}
private static class ConfigurationImpl implements Configuration
{
private final String m_factoryPID;
private final String m_pid;
private String m_bundleLocation;
private Dictionary m_properties;
private boolean m_deleted;
public ConfigurationImpl(String factoryPid, String pid, String bundleLocation)
{
m_factoryPID = factoryPid;
m_pid = pid;
m_bundleLocation = bundleLocation;
}
public void delete() throws IOException
{
m_deleted = true;
}
public String getBundleLocation()
{
return m_bundleLocation;
}
public String getFactoryPid()
{
return m_factoryPID;
}
public String getPid()
{
return m_pid;
}
public Dictionary getProperties()
{
return m_properties;
}
public void setBundleLocation(String bundleLocation)
{
if (m_bundleLocation != null && !m_bundleLocation.equals(bundleLocation))
{
throw new RuntimeException("Configuration already bound to location: " + m_bundleLocation + " (trying to set to: " + bundleLocation + ")");
}
m_bundleLocation = bundleLocation;
}
public void update() throws IOException
{
}
public void update(Dictionary properties) throws IOException
{
m_properties = properties;
}
}
/** Dummy session. */
private static class DeploymentSessionImpl implements DeploymentSession
{
public File getDataFile(Bundle bundle)
{
return null;
}
public DeploymentPackage getSourceDeploymentPackage()
{
return null;
}
public DeploymentPackage getTargetDeploymentPackage()
{
return null;
}
@Override
public String toString()
{
return "Test DeploymentSession @ 0x" + System.identityHashCode(this);
}
}
private static class LogServiceImpl implements LogService
{
private static final String[] LEVEL = { "", "[ERROR]", "[WARN ]", "[INFO ]", "[DEBUG]" };
private Throwable m_exception;
public void failOnException() throws Throwable
{
if (m_exception != null)
{
throw m_exception;
}
}
public void log(int level, String message)
{
System.out.println(LEVEL[level] + " - " + message);
}
public void log(int level, String message, Throwable exception)
{
System.out.println(LEVEL[level] + " - " + message + " - " + exception.getMessage());
m_exception = exception;
}
public void log(ServiceReference sr, int level, String message)
{
System.out.println(LEVEL[level] + " - " + message);
}
public void log(ServiceReference sr, int level, String message, Throwable exception)
{
System.out.println(LEVEL[level] + " - " + message + " - " + exception.getMessage());
m_exception = exception;
}
}
private static class ServiceReferenceImpl implements ServiceReference
{
private final Properties m_properties;
public ServiceReferenceImpl()
{
this(new Properties());
}
public ServiceReferenceImpl(Properties properties)
{
m_properties = properties;
}
public int compareTo(Object reference)
{
return 0;
}
public Bundle getBundle()
{
return null;
}
public Object getProperty(String key)
{
return m_properties.get(key);
}
public String[] getPropertyKeys()
{
return Collections.list(m_properties.keys()).toArray(new String[0]);
}
public Bundle[] getUsingBundles()
{
return null;
}
public boolean isAssignableTo(Bundle bundle, String className)
{
return false;
}
@Override
public String toString()
{
return "Test ConfigAdmin @ 0x" + System.identityHashCode(this);
}
}
private File m_tempDir;
private LogServiceImpl m_logger;
/** Go through a simple session, containing two empty configurations. */
public void testBasicConfigurationSession() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
String config = "<MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.0.0'>\n" +
" <OCD name='ocd' id='ocd'>\n" +
" <AD id='name' type='STRING' cardinality='0' />\n" +
" </OCD>\n" +
" <Designate pid='simple' bundle='osgi-dp:location'>\n" +
" <Object ocdref='ocd'>\n" +
" <Attribute adref='name'>\n" +
" <Value><![CDATA[test]]></Value>\n" +
" </Attribute>\n" +
" </Object>\n" +
" </Designate>\n" +
"</MetaData>\n";
p.process("basic", new ByteArrayInputStream(config.getBytes()));
p.prepare();
p.commit();
p.addConfigurationAdmin(new ServiceReferenceImpl(), new ConfigurationAdminImpl("simple"));
p.postcommit();
m_logger.failOnException();
}
/** Go through a simple session, containing two empty configurations. */
public void testFilteredConfigurationSession() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
String config = "<MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.0.0' filter='(id=42)'>\n" +
" <OCD name='ocd' id='ocd'>\n" +
" <AD id='name' type='STRING' cardinality='0' />\n" +
" </OCD>\n" +
" <Designate pid='simple' bundle='osgi-dp:location'>\n" +
" <Object ocdref='ocd'>\n" +
" <Attribute adref='name'>\n" +
" <Value><![CDATA[test]]></Value>\n" +
" </Attribute>\n" +
" </Object>\n" +
" </Designate>\n" +
"</MetaData>\n";
p.process("basic", new ByteArrayInputStream(config.getBytes()));
p.prepare();
p.commit();
Properties props = new Properties();
props.put("id", Integer.valueOf(42));
ConfigurationAdminImpl ca1 = new ConfigurationAdminImpl("simple");
ConfigurationAdminImpl ca2 = new ConfigurationAdminImpl();
p.addConfigurationAdmin(new ServiceReferenceImpl(props), ca1);
p.addConfigurationAdmin(new ServiceReferenceImpl(), ca2);
p.postcommit();
m_logger.failOnException();
assertEquals("test", ca1.m_configs.get("simple").getProperties().get("name"));
assertTrue(ca2.m_configs.isEmpty());
}
/** Go through a simple session, containing two empty configurations. */
public void testMissingMandatoryValueInConfig() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
String config = "<MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.1.0' filter='(id=42)'>\n" +
" <OCD name='ocd' id='ocd'>\n" +
" <AD id='name' type='Integer' />\n" +
" </OCD>\n" +
" <Designate pid='simple' bundle='osgi-dp:location'>\n" +
" <Object ocdref='ocd'>\n" +
" <Attribute adref='name'>\n" +
" <Value><![CDATA[]]></Value>\n" +
" </Attribute>\n" +
" </Object>\n" +
" </Designate>\n" +
"</MetaData>\n";
try
{
p.process("missing-value", new ByteArrayInputStream(config.getBytes()));
fail("Expected ResourceProcessorException for missing value!");
}
catch (ResourceProcessorException e)
{
// Ok; expected...
assertEquals("Unable to parse value for definition: adref=name", e.getMessage());
}
}
/** Make sure the processor does not accept a 'null' session. */
public void testNullSession() throws Exception
{
AutoConfResourceProcessor p = new AutoConfResourceProcessor();
try
{
p.begin(null);
fail("Should have gotten an exception when trying to begin with null session.");
}
catch (Exception e)
{
// expected
}
}
/** Go through a simple session, containing two empty configurations. */
public void testSimpleInstallAndUninstallSession() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
p.process("a", new ByteArrayInputStream("<MetaData />".getBytes()));
p.prepare();
p.commit();
p.postcommit();
m_logger.failOnException();
createNewSession(p);
p.dropAllResources();
p.prepare();
p.commit();
p.postcommit();
m_logger.failOnException();
}
/** Go through a simple session, containing two empty configurations. */
public void testSimpleSession() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
p.process("a", new ByteArrayInputStream("<MetaData />".getBytes()));
p.process("b", new ByteArrayInputStream("<MetaData />".getBytes()));
p.prepare();
p.commit();
p.postcommit();
m_logger.failOnException();
}
/** Tests that we can update an existing configuration and properly handling deleted & updated configurations. */
public void testUpdateConfigurationSession() throws Throwable
{
AutoConfResourceProcessor p = createAutoConfRP();
createNewSession(p);
String config1 = "<MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.0.0'>" +
"<OCD name='ocd1' id='ocd1'>" +
" <AD id='nameA' type='STRING' cardinality='0' />" +
"</OCD>" +
"<OCD name='ocd2' id='ocd2'>" +
" <AD id='nameB' type='STRING' cardinality='0' />" +
"</OCD>" +
"<Designate pid='pid2' bundle='osgi-dp:location2'>" +
" <Object ocdref='ocd2'>" +
" <Attribute adref='nameB'>" +
" <Value><![CDATA[test2]]></Value>" +
" </Attribute>" +
" </Object>" +
"</Designate>" +
"<Designate pid='pid1' bundle='osgi-dp:location1'>" +
" <Object ocdref='ocd1'>" +
" <Attribute adref='nameA'>" +
" <Value><![CDATA[test1]]></Value>" +
" </Attribute>" +
" </Object>" +
"</Designate>" +
"</MetaData>";
ConfigurationAdminImpl ca = new ConfigurationAdminImpl("pid1", "pid2", "pid3");
p.process("update", new ByteArrayInputStream(config1.getBytes()));
p.prepare();
p.commit();
p.addConfigurationAdmin(new ServiceReferenceImpl(), ca);
p.postcommit();
m_logger.failOnException();
assertEquals(2, ca.m_configs.size());
assertTrue(ca.m_configs.containsKey("pid1"));
assertFalse(ca.m_configs.get("pid1").m_deleted);
assertEquals("test1", ca.m_configs.get("pid1").getProperties().get("nameA"));
assertTrue(ca.m_configs.containsKey("pid2"));
assertFalse(ca.m_configs.get("pid2").m_deleted);
assertEquals("test2", ca.m_configs.get("pid2").getProperties().get("nameB"));
String config2 = "<MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.0.0'>" +
"<OCD name='ocd3' id='ocd3'>" +
" <AD id='nameC' type='STRING' cardinality='0' />" +
"</OCD>" +
"<OCD name='ocd2' id='ocd2'>" +
" <AD id='nameB' type='STRING' cardinality='0' />" +
"</OCD>" +
"<Designate pid='pid2' bundle='osgi-dp:location2'>" +
" <Object ocdref='ocd2'>" +
" <Attribute adref='nameB'>" +
" <Value><![CDATA[test4]]></Value>" +
" </Attribute>" +
" </Object>" +
"</Designate>" +
"<Designate pid='pid3' bundle='osgi-dp:location3'>" +
" <Object ocdref='ocd3'>" +
" <Attribute adref='nameC'>" +
" <Value><![CDATA[test3]]></Value>" +
" </Attribute>" +
" </Object>" +
"</Designate>" +
"</MetaData>";
createNewSession(p);
p.process("update", new ByteArrayInputStream(config2.getBytes()));
p.prepare();
p.commit();
p.addConfigurationAdmin(new ServiceReferenceImpl(), ca);
p.postcommit();
m_logger.failOnException();
assertEquals(3, ca.m_configs.size());
assertTrue(ca.m_configs.containsKey("pid1"));
assertTrue(ca.m_configs.get("pid1").m_deleted);
assertEquals("test1", ca.m_configs.get("pid1").getProperties().get("nameA"));
assertTrue(ca.m_configs.containsKey("pid2"));
assertFalse(ca.m_configs.get("pid2").m_deleted);
assertEquals("test4", ca.m_configs.get("pid2").getProperties().get("nameB"));
assertTrue(ca.m_configs.containsKey("pid3"));
assertFalse(ca.m_configs.get("pid3").m_deleted);
assertEquals("test3", ca.m_configs.get("pid3").getProperties().get("nameC"));
}
@Override
protected void setUp() throws IOException
{
m_tempDir = File.createTempFile("persistence", "dir");
m_tempDir.delete();
m_tempDir.mkdirs();
m_logger = new LogServiceImpl();
}
@Override
protected void tearDown() throws Exception
{
Utils.removeDirectoryWithContent(m_tempDir);
}
private AutoConfResourceProcessor createAutoConfRP()
{
AutoConfResourceProcessor p = new AutoConfResourceProcessor();
Utils.configureObject(p, LogService.class, m_logger);
Utils.configureObject(p, DependencyManager.class, createMockDM());
Utils.configureObject(p, PersistencyManager.class, new PersistencyManager(m_tempDir));
return p;
}
@SuppressWarnings("unused")
private BundleContext createMockBundleContext()
{
return Utils.createMockObjectAdapter(BundleContext.class, new Object()
{
public Filter createFilter(String condition)
{
return Utils.createMockObjectAdapter(Filter.class, new Object()
{
public boolean match(ServiceReference ref)
{
Object id = ref.getProperty("id");
if (id != null && id.equals(Integer.valueOf(42)))
{
return true;
}
return false;
}
public void remove(Component service)
{
}
});
}
});
}
@SuppressWarnings("unused")
private Component createMockComponent()
{
return Utils.createMockObjectAdapter(Component.class, new Object()
{
public DependencyManager getDependencyManager()
{
return new DependencyManager(createMockBundleContext());
}
});
}
private DependencyManager createMockDM()
{
return new DependencyManager(createMockBundleContext())
{
public void remove(Component service)
{
}
};
}
private DeploymentSession createNewSession(AutoConfResourceProcessor p)
{
DeploymentSessionImpl s = new DeploymentSessionImpl();
p.begin(s);
Utils.configureObject(p, Component.class, createMockComponent());
return s;
}
}