FELIX-1891 - IO Connector Service implementation - first commit.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@890151 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/io/pom.xml b/io/pom.xml
new file mode 100644
index 0000000..72a176f
--- /dev/null
+++ b/io/pom.xml
@@ -0,0 +1,107 @@
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.apache.felix</groupId>
+    <artifactId>felix-parent</artifactId>
+    <version>1.2.0</version>
+    <relativePath>../../pom/pom.xml</relativePath>
+  </parent>
+  
+  <modelVersion>4.0.0</modelVersion>
+  <packaging>bundle</packaging>
+  <name>Apache Felix IO Connector Service</name>
+  <version>0.9.0-SNAPSHOT</version>
+  <artifactId>org.apache.felix.io</artifactId>
+  <dependencies>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <version>4.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <version>4.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>${pom.groupId}</groupId>
+      <artifactId>org.osgi.foundation</artifactId>
+      <version>1.0.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <version>1.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Name>Apache Felix IO Connector Service</Bundle-Name>
+            <Bundle-Description>
+              An implementation of the OSGi IO Connector Service
+			</Bundle-Description>
+            <Bundle-Activator> 
+              ${pom.artifactId}.Activator </Bundle-Activator>
+            <Bundle-SymbolicName>
+              ${pom.artifactId} 
+            </Bundle-SymbolicName>
+            <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+            <Export-Package>
+            </Export-Package>
+            <Private-Package>
+              org.apache.felix.io 
+			</Private-Package>
+            <Export-Service>
+              org.osgi.service.io.ConnectorService 
+			</Export-Service>
+            <Include-Resource>
+            </Include-Resource>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rat-maven-plugin</artifactId>
+        <configuration>
+          <excludeSubProjects>false</excludeSubProjects>
+          <useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
+          <useMavenDefaultExcludes>true</useMavenDefaultExcludes>
+          <excludes>
+            <param>doc/*</param>
+            <param>maven-eclipse.xml</param>
+            <param>.checkstyle</param>
+            <param>.externalToolBuilders/*</param>
+          </excludes>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file
diff --git a/io/src/main/java/org/apache/felix/io/Activator.java b/io/src/main/java/org/apache/felix/io/Activator.java
new file mode 100644
index 0000000..bb670da
--- /dev/null
+++ b/io/src/main/java/org/apache/felix/io/Activator.java
@@ -0,0 +1,63 @@
+/**
+ *  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.io;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.io.ConnectorService;
+
+/**
+ * IO Connector bundle Activator.
+ * 
+ * @version $Rev$ $Date$
+ */
+public class Activator implements BundleActivator
+{
+
+    private ServiceRegistration m_registration;
+    private ConnectorServiceImpl m_connectorService;
+
+    /**
+     * Called when IO Connector bundle is started. Creates and registers IO Connector Service.
+     * 
+     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+     */
+    public void start(BundleContext context) throws Exception
+    {
+        m_connectorService = new ConnectorServiceImpl(context);
+        context.registerService(ConnectorService.class.getName(), m_connectorService, null);
+    }
+
+    /**
+     * Called when IO Connector bundle is stopped. Stops IO Connector Service and unregisters the service.
+     * 
+     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+     */
+    public void stop(BundleContext context) throws Exception
+    {
+        if (m_connectorService != null)
+        {
+            m_connectorService.stop();
+        }
+        if (m_registration != null)
+        {
+            m_registration.unregister();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/io/src/main/java/org/apache/felix/io/ConnectorServiceImpl.java b/io/src/main/java/org/apache/felix/io/ConnectorServiceImpl.java
new file mode 100644
index 0000000..a039381
--- /dev/null
+++ b/io/src/main/java/org/apache/felix/io/ConnectorServiceImpl.java
@@ -0,0 +1,342 @@
+/**
+ *  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.io;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.microedition.io.Connection;
+import javax.microedition.io.ConnectionNotFoundException;
+import javax.microedition.io.Connector;
+import javax.microedition.io.InputConnection;
+import javax.microedition.io.OutputConnection;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.io.ConnectionFactory;
+import org.osgi.service.io.ConnectorService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * <p>
+ * The Connector Service should be called to create and open <code>javax.microedition.io.Connection</code> objects.
+ * </p>
+ * 
+ * @see ConnectorService
+ * @version $Rev$ $Date$
+ */
+public class ConnectorServiceImpl implements ConnectorService
+{
+
+    ServiceTracker m_connFactoryTracker;
+
+    /**
+     * Constructs new ConnectorService.
+     * 
+     * @param context
+     *            bundleContext @see {@link BundleContext}.
+     */
+    public ConnectorServiceImpl(BundleContext context)
+    {
+        this.m_connFactoryTracker = new ServiceTracker(context, ConnectionFactory.class.getName(), null);
+        m_connFactoryTracker.open();
+    }
+
+    /**
+     * Stops ConnectorService. This method closes {@link ConnectionFactory} serviceTracker.
+     * 
+     * @see ServiceTracker#close()
+     */
+    public void stop()
+    {
+        m_connFactoryTracker.close();
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#openDataInputStream(java.lang.String)
+     */
+    public DataInputStream openDataInputStream(String name) throws IOException
+    {
+        Connection connection = open(name, READ, false);
+        if (!(connection instanceof InputConnection))
+        {
+            try
+            {
+                connection.close();
+            } catch (IOException ioex)
+            {
+
+            }
+
+            throw new IOException("Connection doesn't implement InputConnection" + connection.getClass());
+        }
+
+        return ((InputConnection) connection).openDataInputStream();
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#openDataOutputStream(java.lang.String)
+     */
+    public DataOutputStream openDataOutputStream(String name) throws IOException
+    {
+        Connection connection = open(name, WRITE, false);
+        if (!(connection instanceof OutputConnection))
+        {
+            try
+            {
+                connection.close();
+            } catch (IOException ioex)
+            {
+
+            }
+
+            throw new IOException("Connection doesn't implement OutputConnection" + connection.getClass());
+        }
+
+        return ((OutputConnection) connection).openDataOutputStream();
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#openInputStream(java.lang.String)
+     */
+    public InputStream openInputStream(String name) throws IOException
+    {
+        Connection connection = open(name, READ, false);
+        if (!(connection instanceof InputConnection))
+        {
+            try
+            {
+                connection.close();
+            } catch (IOException ioex)
+            {
+
+            }
+
+            throw new IOException("Connection doesn't implement InputConnection" + connection.getClass());
+        }
+
+        return ((InputConnection) connection).openInputStream();
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#openOutputStream(java.lang.String)
+     */
+    public OutputStream openOutputStream(String name) throws IOException
+    {
+        Connection connection = open(name, WRITE, false);
+        if (!(connection instanceof OutputConnection))
+        {
+            try
+            {
+                connection.close();
+            } catch (IOException ioex)
+            {
+
+            }
+
+            throw new IOException("Connection doesn't implement OutputConnection" + connection.getClass());
+        }
+
+        return ((OutputConnection) connection).openOutputStream();
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#open(String)
+     */
+    public Connection open(String name) throws IOException
+    {
+        return open(name, READ_WRITE, false);
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#open(String, int)
+     */
+    public Connection open(String name, int mode) throws IOException
+    {
+        return open(name, mode, false);
+    }
+
+    /**
+     * @see org.osgi.service.io.ConnectorService#open(String, int, boolean)
+     */
+    public Connection open(String name, int mode, boolean timeouts) throws IOException
+    {
+        if (name == null)
+        {
+            throw new IllegalArgumentException("URI for the connection can't be null!");
+        }
+
+        // resolving scheme name
+        int index = name.indexOf(":");
+        if (index == -1)
+        {
+            throw new IllegalArgumentException("Can't resolve scheme name");
+        }
+
+        String scheme = name.substring(0, index);
+
+        ConnectionFactory connFactory = resolveConnectionFactory(scheme);
+        Connection connection = null;
+        if (connFactory != null)
+        {
+            connection = connFactory.createConnection(name, mode, timeouts);
+        }
+        // if connection is not provided go to javax.microedition.io.Connector
+        if (connection == null)
+        {
+            try
+            {
+                connection = Connector.open(name, mode, timeouts);
+            } catch (Exception ex)
+            {
+
+            }
+
+        } else
+        {
+            return connection;
+        }
+
+        throw new ConnectionNotFoundException("Failed to create connection " + name);
+    }
+
+    /**
+     * <p>
+     * Resolves {@link ConnectionFactory} based on IO scheme name. If multiple ConnectionFactory services register with
+     * the same scheme, method select the ConnectionFactory with highest value for service.ranking service registration
+     * property or if more than one ConnectionFactory service has the highest value, the ConnectionFactory service with
+     * the lowest service.id is selected.
+     * </p>
+     * 
+     * @param scheme
+     *            name of IO scheme.
+     * @return {@link ConnectionFactory} which matched provided scheme.
+     */
+    private ConnectionFactory resolveConnectionFactory(String scheme)
+    {
+        ServiceReference[] references = m_connFactoryTracker.getServiceReferences();
+        if (references == null || references.length == 0)
+        {
+            return null;
+        }
+
+        ServiceReference matchingRef = null;
+        for (int i = 0; i < references.length; i++)
+        {
+            if (containsScheme(references[i], scheme))
+            {
+                if (matchingRef != null)
+                {
+                    int matchRanking = getServiceRanking(matchingRef);
+                    int foundRanking = getServiceRanking(references[i]);
+                    if (foundRanking > matchRanking)
+                    {
+                        matchingRef = references[i];
+                    } else if (foundRanking == matchRanking && compareServiceId(references[i], matchingRef))
+                    {
+                        matchingRef = references[i];
+                    }
+                } else
+                {
+                    matchingRef = references[i];
+                }
+            }
+        }
+        if (matchingRef != null)
+        {
+            return (ConnectionFactory) m_connFactoryTracker.getService(matchingRef);
+        }
+        return null;
+    }
+
+    /**
+     * Checks if provided {@link ServiceReference} contains in its service properties provided scheme name.
+     * 
+     * @param ref
+     *            {@link ServiceReference}.
+     * @param scheme
+     *            name of IO scheme.
+     * @return true if contains scheme name, false if not.
+     */
+    private boolean containsScheme(ServiceReference ref, String scheme)
+    {
+        Object property = ref.getProperty(ConnectionFactory.IO_SCHEME);
+        if (property != null && property.equals(scheme))
+        {
+            return true;
+        } else
+        {
+            if (property != null && property instanceof String[])
+            {
+                String[] schemes = (String[]) property;
+                for (int index = 0; index < schemes.length; index++)
+                {
+                    if (schemes[index].equals(scheme))
+                    {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Compare service.id of two {@link ServiceReference}'s.
+     * 
+     * @param foundRef
+     *            {@link ServiceReference}.
+     * @param prevRef
+     *            {@link ServiceReference}.
+     * @return true if first reference has lowest service.id than second, false if not.
+     */
+    private boolean compareServiceId(ServiceReference foundRef, ServiceReference prevRef)
+    {
+        Long foundServiceId = (Long) foundRef.getProperty(Constants.SERVICE_ID);
+        Long prevServiceId = (Long) prevRef.getProperty(Constants.SERVICE_ID);
+        if (foundServiceId.longValue() < prevServiceId.longValue())
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets service.ranking from provided {@link ServiceReference}.
+     * 
+     * @param ref
+     *            {@link ServiceReference}.
+     * @return service.ranking property value.
+     */
+    private int getServiceRanking(ServiceReference ref)
+    {
+        Object property = ref.getProperty(Constants.SERVICE_RANKING);
+        if (property == null || !(property instanceof Integer))
+        {
+            return 0;
+        } else
+        {
+            return ((Integer) property).intValue();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/io/src/test/java/org/apache/felix/io/ConnectionFactoryMock.java b/io/src/test/java/org/apache/felix/io/ConnectionFactoryMock.java
new file mode 100644
index 0000000..7632419
--- /dev/null
+++ b/io/src/test/java/org/apache/felix/io/ConnectionFactoryMock.java
@@ -0,0 +1,131 @@
+/**
+ *  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.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.microedition.io.Connection;
+import javax.microedition.io.InputConnection;
+import javax.microedition.io.OutputConnection;
+
+import org.osgi.service.io.ConnectionFactory;
+
+/**
+ * {@link ConnectionFactory} implementation for testing purpose. Creates fake connection @see {@link Connection}.
+ * 
+ * 
+ * @version $Rev$ $Date$
+ */
+public class ConnectionFactoryMock implements ConnectionFactory
+{
+
+    /**
+     * uri name.
+     */
+    private String m_name;
+    /**
+     * access mode.
+     */
+    private int m_mode;
+    /**
+     * connection timeout.
+     */
+    private boolean m_timeout;
+
+    /**
+     * @see org.osgi.service.io.ConnectionFactory#createConnection(String, int, boolean)
+     */
+    public Connection createConnection(String name, int mode, boolean timeout) throws IOException
+    {
+        this.m_mode = mode;
+        this.m_name = name;
+        this.m_timeout = timeout;
+        return new TestConnection();
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public int getMode()
+    {
+        return m_mode;
+    }
+
+    public boolean isTimeout()
+    {
+        return m_timeout;
+    }
+
+    /**
+     * Mock implementation of {@link Connection}, {@link InputConnection}, {@link OutputConnection}.
+     * 
+     */
+    private class TestConnection implements Connection, InputConnection, OutputConnection
+    {
+
+        /**
+         * @see javax.microedition.io.Connection#close()
+         */
+        public void close() throws IOException
+        {
+
+        }
+
+        /**
+         * @see javax.microedition.io.InputConnection#openDataInputStream()
+         */
+        public DataInputStream openDataInputStream() throws IOException
+        {
+            return new DataInputStream(new ByteArrayInputStream(new byte[]
+            {}));
+        }
+
+        /**
+         * @see javax.microedition.io.InputConnection#openInputStream()
+         */
+        public InputStream openInputStream() throws IOException
+        {
+            return new ByteArrayInputStream(new byte[]
+            {});
+        }
+
+        /**
+         * @see javax.microedition.io.OutputConnection#openDataOutputStream()
+         */
+        public DataOutputStream openDataOutputStream() throws IOException
+        {
+            return new DataOutputStream(new ByteArrayOutputStream());
+        }
+
+        /**
+         * @see javax.microedition.io.OutputConnection#openOutputStream()
+         */
+        public OutputStream openOutputStream() throws IOException
+        {
+            return new ByteArrayOutputStream();
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/io/src/test/java/org/apache/felix/io/ConnectorServiceTest.java b/io/src/test/java/org/apache/felix/io/ConnectorServiceTest.java
new file mode 100644
index 0000000..ee7bb32
--- /dev/null
+++ b/io/src/test/java/org/apache/felix/io/ConnectorServiceTest.java
@@ -0,0 +1,294 @@
+/**
+ *  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.io;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.microedition.io.Connection;
+import javax.microedition.io.ConnectionNotFoundException;
+
+import junit.framework.TestCase;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.io.ConnectionFactory;
+import org.osgi.service.io.ConnectorService;
+
+/**
+ * <tt>ConnectorServiceTest</tt> represents test class for {@link ConnectorService}.
+ * 
+ * @version $Rev$ $Date$
+ */
+public class ConnectorServiceTest extends TestCase
+{
+
+    private BundleContext m_bundleContext;
+    private ConnectorService m_service;
+    private ServiceListener m_serviceListener;
+    private long m_serviceId;
+
+    private ServiceRegistration registerConnectionFactory(ConnectionFactoryMock connFactory, Dictionary props)
+    {
+        // service reference for ConnectionFactory service
+        ServiceReference reference = (ServiceReference) Mockito.mock(ServiceReference.class);
+        Mockito.when(reference.getProperty(ConnectionFactory.IO_SCHEME)).thenReturn(
+                props.get(ConnectionFactory.IO_SCHEME));
+        Mockito.when(reference.getProperty(Constants.SERVICE_ID)).thenReturn(new Long(++m_serviceId));
+        Mockito.when(reference.getProperty(Constants.SERVICE_RANKING)).thenReturn(props.get(Constants.SERVICE_RANKING));
+        // service registration for ConnectionFactory service
+        ServiceRegistration registration = (ServiceRegistration) Mockito.mock(ServiceRegistration.class);
+        Mockito.when(registration.getReference()).thenReturn(reference);
+        // service event
+        ServiceEvent registeredEvent = (ServiceEvent) Mockito.mock(ServiceEvent.class);
+        Mockito.when(registeredEvent.getServiceReference()).thenReturn(reference);
+        Mockito.when(new Integer(registeredEvent.getType())).thenReturn(new Integer(ServiceEvent.REGISTERED));
+        Mockito.when(m_bundleContext.getService(reference)).thenReturn(connFactory);
+        // sending registration event
+        // service tracker for ConnectionFactory service used by ConnectorService
+        // will be informed about service registration
+        m_serviceListener.serviceChanged(registeredEvent);
+
+        return registration;
+    }
+
+    private ConnectorService getConnectorService()
+    {
+        return m_service;
+    }
+
+    public void setUp()
+    {
+        m_bundleContext = (BundleContext) Mockito.mock(BundleContext.class);
+        ArgumentCaptor argument = ArgumentCaptor.forClass(ServiceListener.class);
+        m_service = new ConnectorServiceImpl(m_bundleContext);
+        try
+        {
+            ((BundleContext) Mockito.verify(m_bundleContext)).addServiceListener((ServiceListener) argument.capture(),
+                    Mockito.anyString());
+        } catch (InvalidSyntaxException e)
+        {
+            fail();
+        }
+        // getting from captor serviceListener which listen to ConnectionFactory service
+        m_serviceListener = (ServiceListener) argument.getValue();
+    }
+
+    public void tearDown()
+    {
+
+    }
+
+    /**
+     * Tests all methods provided by {@link ConnectorService}.
+     * 
+     * @throws Exception
+     */
+    public void testOpen() throws Exception
+    {
+        ConnectionFactoryMock connFactory = new ConnectionFactoryMock();
+        Dictionary props = new Hashtable();
+        props.put(ConnectionFactory.IO_SCHEME, "file");
+        ServiceRegistration registration = registerConnectionFactory(connFactory, props);
+        ConnectorService service = getConnectorService();
+
+        Connection connection = service.open("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ_WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned Connection", connection);
+
+        connection = service.open("file://test.txt", ConnectorService.READ);
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned Connection", connection);
+
+        connection = service.open("file://test.txt", ConnectorService.WRITE);
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned Connection", connection);
+
+        connection = service.open("file://test.txt", ConnectorService.READ, true);
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(true, connFactory.isTimeout());
+        assertNotNull("checks returned Connection", connection);
+
+        try
+        {
+            connection = service.open("http://test.txt", ConnectorService.READ);
+            fail("Connection shouldn't be created");
+        } catch (ConnectionNotFoundException e)
+        {
+            // "expected"
+        }
+
+        try
+        {
+            service.open("file.txt");
+            fail("Illegal format of uri");
+        } catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+
+        DataInputStream dataInStream = service.openDataInputStream("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned DataInputStream", dataInStream);
+
+        DataOutputStream dataOutStream = service.openDataOutputStream("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned DataOutputStream", dataOutStream);
+
+        InputStream inStream = service.openInputStream("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned InputStream", inStream);
+
+        OutputStream outStream = service.openOutputStream("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned OutputStream", outStream);
+
+        registration.unregister();
+    }
+
+    /**
+     * Registers two ConnectionFactory services with same IO_SCHEME. One with higher service.ranking Connector Service
+     * should pickup service with highest service.ranking.
+     * 
+     * @throws Exception
+     */
+    public void testHighestRanking() throws Exception
+    {
+        ConnectionFactoryMock connFactory = new ConnectionFactoryMock();
+        Dictionary props = new Hashtable();
+        props.put(ConnectionFactory.IO_SCHEME, "file");
+        registerConnectionFactory(connFactory, props);
+
+        ConnectionFactoryMock connFactory2 = new ConnectionFactoryMock();
+        props.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE));
+        registerConnectionFactory(connFactory2, props);
+
+        ConnectorService service = getConnectorService();
+
+        Connection connection = service.open("file://test.txt");
+        assertEquals("uri checks for lowest ranking service", null, connFactory.getName());
+        assertEquals("file://test.txt", connFactory2.getName());
+        assertEquals(ConnectorService.READ_WRITE, connFactory2.getMode());
+        assertEquals(false, connFactory2.isTimeout());
+        assertNotNull("Connection should be created", connection);
+    }
+
+    /**
+     * Registers two ConnectionFactory services with same IO_SCHEME. Connector Service should pickup service with lowest
+     * service.id.
+     * 
+     * @throws Exception
+     */
+    public void testLowestServiceId() throws Exception
+    {
+        ConnectionFactoryMock connFactory = new ConnectionFactoryMock();
+        Dictionary props = new Hashtable();
+        props.put(ConnectionFactory.IO_SCHEME, "file");
+        ServiceRegistration registration = registerConnectionFactory(connFactory, props);
+
+        ConnectionFactoryMock connFactory2 = new ConnectionFactoryMock();
+        ServiceRegistration registration2 = registerConnectionFactory(connFactory2, props);
+
+        ConnectorService service = getConnectorService();
+
+        Connection connection = service.open("file://test.txt");
+        assertEquals("uri checks for highest service.id", null, connFactory2.getName());
+        assertEquals("uri checks for lowest service.id", "file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ_WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned Connection", connection);
+
+        Long serviceId1 = (Long) registration.getReference().getProperty(Constants.SERVICE_ID);
+        Long serviceId2 = (Long) registration2.getReference().getProperty(Constants.SERVICE_ID);
+
+        assertTrue(serviceId1.longValue() < serviceId2.longValue());
+
+        registration.unregister();
+        registration2.unregister();
+    }
+
+    /**
+     * Tests ConnectionFactory service which support 3 different schemes.
+     * 
+     * @throws Exception
+     */
+    public void testMultipleScheme() throws Exception
+    {
+        ConnectionFactoryMock connFactory = new ConnectionFactoryMock();
+        Dictionary props = new Hashtable();
+        props.put(ConnectionFactory.IO_SCHEME, new String[]
+        { "file", "http", "sms" });
+        ServiceRegistration registration = registerConnectionFactory(connFactory, props);
+        ConnectorService service = getConnectorService();
+
+        Connection connection = service.open("file://test.txt");
+        assertEquals("file://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ_WRITE, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned connection", connection);
+
+        connection = service.open("http://test.txt", ConnectorService.READ);
+        assertEquals("http://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned connection", connection);
+
+        connection = service.open("sms://test.txt", ConnectorService.READ);
+        assertEquals("sms://test.txt", connFactory.getName());
+        assertEquals(ConnectorService.READ, connFactory.getMode());
+        assertEquals(false, connFactory.isTimeout());
+        assertNotNull("checks returned connection", connection);
+
+        try
+        {
+            connection = service.open("ftp://test.txt", ConnectorService.READ);
+            fail("Connection shouldn't be created");
+        } catch (ConnectionNotFoundException e)
+        {
+            // "expected"
+        }
+
+        registration.unregister();
+    }
+
+}
\ No newline at end of file