diff --git a/io/pom.xml b/io/pom.xml new file mode 100644 index 00000000000..72a176fac27 --- /dev/null +++ b/io/pom.xml @@ -0,0 +1,107 @@ + + + + org.apache.felix + felix-parent + 1.2.0 + ../../pom/pom.xml + + + 4.0.0 + bundle + Apache Felix IO Connector Service + 0.9.0-SNAPSHOT + org.apache.felix.io + + + org.osgi + org.osgi.core + 4.0.0 + provided + + + org.osgi + org.osgi.compendium + 4.0.0 + provided + + + ${pom.groupId} + org.osgi.foundation + 1.0.0 + provided + + + org.mockito + mockito-all + 1.8.1 + test + + + + + + org.apache.felix + maven-bundle-plugin + true + + + Apache Felix IO Connector Service + + An implementation of the OSGi IO Connector Service + + + ${pom.artifactId}.Activator + + ${pom.artifactId} + + The Apache Software Foundation + + + + org.apache.felix.io + + + org.osgi.service.io.ConnectorService + + + + + + + + org.codehaus.mojo + rat-maven-plugin + + false + true + true + + doc/* + maven-eclipse.xml + .checkstyle + .externalToolBuilders/* + + + + + + \ 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 00000000000..bb670da1c10 --- /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 00000000000..a0393813072 --- /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; + +/** + *

+ * The Connector Service should be called to create and open javax.microedition.io.Connection objects. + *

+ * + * @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); + } + + /** + *

+ * 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. + *

+ * + * @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 00000000000..7632419d669 --- /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 00000000000..ee7bb3273f4 --- /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; + +/** + * ConnectorServiceTest 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