FELIX-3329 FELIX-3330 Added a couple of test cases to validate some of the basic operations in this resource processor.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1241559 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/AutoConfResourceProcessorTest.java b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/AutoConfResourceProcessorTest.java
new file mode 100644
index 0000000..d3d8ca0
--- /dev/null
+++ b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/AutoConfResourceProcessorTest.java
@@ -0,0 +1,386 @@
+/*
+ * 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.Dictionary;
+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.log.LogService;
+
+public class AutoConfResourceProcessorTest extends TestCase {
+ /** 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 testSimpleSession() throws Exception {
+ AutoConfResourceProcessor p = new AutoConfResourceProcessor();
+ TestUtils.configureObject(p, LogService.class);
+ TestUtils.configureObject(p, Component.class, TestUtils.createMockObjectAdapter(Component.class, new Object() {
+ public DependencyManager getDependencyManager() {
+ return new DependencyManager((BundleContext) TestUtils.createNullObject(BundleContext.class));
+ }
+ }));
+ File tempDir = File.createTempFile("persistence", "dir");
+ tempDir.delete();
+ tempDir.mkdirs();
+
+ System.out.println("Temporary dir: " + tempDir);
+
+ TestUtils.configureObject(p, PersistencyManager.class, new PersistencyManager(tempDir));
+ Session s = new Session();
+ p.begin(s);
+ p.process("a", new ByteArrayInputStream("<MetaData />".getBytes()));
+ p.process("b", new ByteArrayInputStream("<MetaData />".getBytes()));
+ p.prepare();
+ p.commit();
+ p.postcommit();
+ TestUtils.removeDirectoryWithContent(tempDir);
+ }
+
+ /** Go through a simple session, containing two empty configurations. */
+ public void testSimpleInstallAndUninstallSession() throws Throwable {
+ AutoConfResourceProcessor p = new AutoConfResourceProcessor();
+ TestUtils.configureObject(p, LogService.class);
+ TestUtils.configureObject(p, Component.class, TestUtils.createMockObjectAdapter(Component.class, new Object() {
+ public DependencyManager getDependencyManager() {
+ return new DependencyManager((BundleContext) TestUtils.createNullObject(BundleContext.class));
+ }
+ }));
+ Logger logger = new Logger();
+ TestUtils.configureObject(p, LogService.class, logger);
+ File tempDir = File.createTempFile("persistence", "dir");
+ tempDir.delete();
+ tempDir.mkdirs();
+
+ System.out.println("Temporary dir: " + tempDir);
+
+ TestUtils.configureObject(p, PersistencyManager.class, new PersistencyManager(tempDir));
+ Session s = new Session();
+ p.begin(s);
+ p.process("a", new ByteArrayInputStream("<MetaData />".getBytes()));
+ p.prepare();
+ p.commit();
+ p.postcommit();
+ logger.failOnException();
+ s = new Session();
+ p.begin(s);
+ p.dropped("a");
+ p.prepare();
+ p.commit();
+ p.postcommit();
+ logger.failOnException();
+ TestUtils.removeDirectoryWithContent(tempDir);
+ }
+
+ /** Go through a simple session, containing two empty configurations. */
+ public void testBasicConfigurationSession() throws Throwable {
+ AutoConfResourceProcessor p = new AutoConfResourceProcessor();
+ Logger logger = new Logger();
+ TestUtils.configureObject(p, LogService.class, logger);
+ TestUtils.configureObject(p, Component.class, TestUtils.createMockObjectAdapter(Component.class, new Object() {
+ public DependencyManager getDependencyManager() {
+ return new DependencyManager((BundleContext) TestUtils.createNullObject(BundleContext.class));
+ }
+ }));
+ File tempDir = File.createTempFile("persistence", "dir");
+ tempDir.delete();
+ tempDir.mkdirs();
+
+ System.out.println("Temporary dir: " + tempDir);
+
+ TestUtils.configureObject(p, PersistencyManager.class, new PersistencyManager(tempDir));
+ Session s = new Session();
+ p.begin(s);
+ 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(null, new ConfigurationAdmin() {
+ public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException {
+ return null;
+ }
+
+ public Configuration getConfiguration(String pid, String location) throws IOException {
+ return new ConfigurationImpl();
+ }
+
+ public Configuration getConfiguration(String pid) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
+ return null;
+ }
+ });
+ p.postcommit();
+ logger.failOnException();
+ TestUtils.removeDirectoryWithContent(tempDir);
+ }
+
+ /** Go through a simple session, containing two empty configurations. */
+ public void testFilteredConfigurationSession() throws Throwable {
+ AutoConfResourceProcessor p = new AutoConfResourceProcessor();
+ Logger logger = new Logger();
+ TestUtils.configureObject(p, LogService.class, logger);
+ TestUtils.configureObject(p, Component.class, TestUtils.createMockObjectAdapter(Component.class, new Object() {
+ public DependencyManager getDependencyManager() {
+ return new DependencyManager((BundleContext) TestUtils.createNullObject(BundleContext.class));
+ }
+ }));
+ TestUtils.configureObject(p, BundleContext.class, TestUtils.createMockObjectAdapter(BundleContext.class, new Object() {
+ public Filter createFilter(String condition) {
+ return (Filter) TestUtils.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;
+ }
+ });
+ }
+ }));
+ File tempDir = File.createTempFile("persistence", "dir");
+ tempDir.delete();
+ tempDir.mkdirs();
+
+ System.out.println("Temporary dir: " + tempDir);
+
+ TestUtils.configureObject(p, PersistencyManager.class, new PersistencyManager(tempDir));
+ Session s = new Session();
+ p.begin(s);
+ 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));
+ final Configuration configuration = new ConfigurationImpl();
+ p.addConfigurationAdmin(new Reference(props), new ConfigurationAdmin() {
+ public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException {
+ return null;
+ }
+
+ public Configuration getConfiguration(String pid, String location) throws IOException {
+ return configuration;
+ }
+
+ public Configuration getConfiguration(String pid) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
+ return null;
+ }
+ });
+
+ final Configuration emptyConfiguration = new ConfigurationImpl();
+ p.addConfigurationAdmin(new Reference(new Properties()), new ConfigurationAdmin() {
+ public Configuration[] listConfigurations(String filter) throws IOException, InvalidSyntaxException {
+ return null;
+ }
+
+ public Configuration getConfiguration(String pid, String location) throws IOException {
+ return emptyConfiguration;
+ }
+
+ public Configuration getConfiguration(String pid) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid, String location) throws IOException {
+ return null;
+ }
+
+ public Configuration createFactoryConfiguration(String factoryPid) throws IOException {
+ return null;
+ }
+ });
+ p.postcommit();
+ logger.failOnException();
+ assertEquals("test", configuration.getProperties().get("name"));
+ assertNull(emptyConfiguration.getProperties());
+ TestUtils.removeDirectoryWithContent(tempDir);
+ }
+
+ private static class ConfigurationImpl implements Configuration {
+ private String m_bundleLocation = "osgi-dp:location";
+ private Dictionary m_properties;
+
+ public String getPid() {
+ return null;
+ }
+
+ public Dictionary getProperties() {
+ return m_properties;
+ }
+
+ public void update(Dictionary properties) throws IOException {
+ m_properties = properties;
+ }
+
+ public void delete() throws IOException {
+ }
+
+ public String getFactoryPid() {
+ return null;
+ }
+
+ public void update() throws IOException {
+ }
+
+ public void setBundleLocation(String bundleLocation) {
+ m_bundleLocation = bundleLocation;
+ }
+
+ public String getBundleLocation() {
+ return m_bundleLocation;
+ }
+ }
+
+ /** Dummy session. */
+ private static class Session implements DeploymentSession {
+ public DeploymentPackage getTargetDeploymentPackage() {
+ return null;
+ }
+ public DeploymentPackage getSourceDeploymentPackage() {
+ return null;
+ }
+ public File getDataFile(Bundle bundle) {
+ return null;
+ }
+ }
+
+ private static class Logger implements LogService {
+ private static final String[] LEVEL = { "", "[ERROR]", "[WARN ]", "[INFO ]", "[DEBUG]" };
+ private Throwable 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;
+ }
+
+ public void failOnException() throws Throwable {
+ if (m_exception != null) {
+ throw m_exception;
+ }
+ }
+ }
+ private static class Reference implements ServiceReference {
+ private final Dictionary m_properties;
+
+ public Reference(Dictionary properties) {
+ m_properties = properties;
+ }
+
+ public Object getProperty(String key) {
+ return m_properties.get(key);
+ }
+
+ public String[] getPropertyKeys() {
+ return null;
+ }
+
+ public Bundle getBundle() {
+ return null;
+ }
+
+ public Bundle[] getUsingBundles() {
+ return null;
+ }
+
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ return false;
+ }
+
+ public int compareTo(Object reference) {
+ return 0;
+ }
+ }
+}
diff --git a/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/DefaultNullObject.java b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/DefaultNullObject.java
new file mode 100644
index 0000000..46e8d11
--- /dev/null
+++ b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/DefaultNullObject.java
@@ -0,0 +1,71 @@
+/*
+ * 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.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+
+/**
+ * Default null object implementation. Uses a dynamic proxy. Null objects are used
+ * as placeholders for services that are not available.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DefaultNullObject implements InvocationHandler {
+ private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
+ private static final Byte DEFAULT_BYTE = new Byte((byte) 0);
+ private static final Short DEFAULT_SHORT = new Short((short) 0);
+ private static final Integer DEFAULT_INT = new Integer(0);
+ private static final Long DEFAULT_LONG = new Long(0);
+ private static final Float DEFAULT_FLOAT = new Float(0.0f);
+ private static final Double DEFAULT_DOUBLE = new Double(0.0);
+
+ /**
+ * Invokes a method on this null object. The method will return a default
+ * value without doing anything.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Class returnType = method.getReturnType();
+ if (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)) {
+ return DEFAULT_BOOLEAN;
+ }
+ else if (returnType.equals(Byte.class) || returnType.equals(Byte.TYPE)) {
+ return DEFAULT_BYTE;
+ }
+ else if (returnType.equals(Short.class) || returnType.equals(Short.TYPE)) {
+ return DEFAULT_SHORT;
+ }
+ else if (returnType.equals(Integer.class) || returnType.equals(Integer.TYPE)) {
+ return DEFAULT_INT;
+ }
+ else if (returnType.equals(Long.class) || returnType.equals(Long.TYPE)) {
+ return DEFAULT_LONG;
+ }
+ else if (returnType.equals(Float.class) || returnType.equals(Float.TYPE)) {
+ return DEFAULT_FLOAT;
+ }
+ else if (returnType.equals(Double.class) || returnType.equals(Double.TYPE)) {
+ return DEFAULT_DOUBLE;
+ }
+ else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/TestUtils.java b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/TestUtils.java
new file mode 100644
index 0000000..fecaab5
--- /dev/null
+++ b/deploymentadmin/autoconf/src/test/java/org/apache/felix/deployment/rp/autoconf/TestUtils.java
@@ -0,0 +1,129 @@
+/*
+ * 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.File;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Utility class that injects dependencies. Can be used to unit test service implementations.
+ */
+public class TestUtils {
+ /**
+ * Configures an object to use a null object for the specified service interface.
+ *
+ * @param object the object
+ * @param iface the service interface
+ */
+ public static void configureObject(Object object, Class iface) {
+ configureObject(object, iface, createNullObject(iface));
+ }
+
+ /**
+ * Creates a null object for a service interface.
+ *
+ * @param iface the service interface
+ * @return a null object
+ */
+ public static Object createNullObject(Class iface) {
+ return Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new DefaultNullObject());
+ }
+
+ /**
+ * Wraps the given handler in an adapter that will try to pass on received invocations to the hander if that has
+ * an applicable methods else it defaults to a NullObject.
+ *
+ * @param iface the service interface
+ * @param handler the handler to pass invocations to.
+ * @return an adapter that will try to pass on received invocations to the given handler
+ */
+ public static Object createMockObjectAdapter(Class iface, final Object handler) {
+ return Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new DefaultNullObject() {
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ Method bridge = handler.getClass().getMethod(method.getName(), method.getParameterTypes());
+ bridge.setAccessible(true);
+ return bridge.invoke(handler, args);
+ }
+ catch (NoSuchMethodException ex) {
+ return super.invoke(proxy, method, args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getCause();
+ }
+ }
+ });
+ }
+
+ /**
+ * Configures an object to use a specific implementation for the specified service interface.
+ *
+ * @param object the object
+ * @param iface the service interface
+ * @param instance the implementation
+ */
+ public static void configureObject(Object object, Class iface, Object instance) {
+ Class serviceClazz = object.getClass();
+
+ while (serviceClazz != null) {
+ Field[] fields = serviceClazz.getDeclaredFields();
+ AccessibleObject.setAccessible(fields, true);
+ for (int j = 0; j < fields.length; j++) {
+ if (fields[j].getType().equals(iface)) {
+ try {
+ // synchronized makes sure the field is actually written to immediately
+ synchronized (new Object()) {
+ fields[j].set(object, instance);
+ }
+ }
+ catch (Exception e) {
+ throw new IllegalStateException("Could not set field " + fields[j].getName() + " on " + object);
+ }
+ }
+ }
+ serviceClazz = serviceClazz.getSuperclass();
+ }
+ }
+
+ /**
+ * Remove the given directory and all it's files and subdirectories
+ *
+ * @param directory the name of the directory to remove
+ */
+ public static void removeDirectoryWithContent(File directory) {
+ if ((directory == null) || !directory.exists()) {
+ return;
+ }
+ File[] filesAndSubDirs = directory.listFiles();
+ for (int i=0; i < filesAndSubDirs.length; i++) {
+ File file = filesAndSubDirs[i];
+ if (file.isDirectory()) {
+ removeDirectoryWithContent(file);
+ }
+ // else just remove the file
+ file.delete();
+ }
+ directory.delete();
+ }
+}