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();
+    }
+}