Skip to content

Commit 5b34f5a

Browse files
author
Maik Scheibler
committed
use Java SPI to load and instantiate NetworkModules
The usage of the Java Service Provider Interface enables the deployment of additional custom NetworkModules without modifications of the Paho code. Signed-off-by: Maik Scheibler <eclipse@scheibler-family.de>
1 parent a04aaea commit 5b34f5a

14 files changed

Lines changed: 625 additions & 196 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.eclipse.paho.client.mqttv3.internal;
2+
3+
import java.net.URI;
4+
import java.net.URISyntaxException;
5+
6+
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
7+
import org.eclipse.paho.client.mqttv3.MqttException;
8+
import org.junit.Test;
9+
10+
import static org.junit.Assert.assertEquals;
11+
import static org.junit.Assert.assertNull;
12+
import static org.junit.Assert.assertTrue;
13+
14+
public class NetworkModuleServiceTest {
15+
16+
@Test
17+
public void testValidateURI() {
18+
NetworkModuleService.validateURI("tcp://host_literal:1883");
19+
NetworkModuleService.validateURI("ssl://host_literal:8883");
20+
NetworkModuleService.validateURI("ws://host_literal:80/path/to/ws");
21+
NetworkModuleService.validateURI("wss://host_literal:443/path/to/ws");
22+
}
23+
24+
@Test(expected = IllegalArgumentException.class)
25+
public void failInvalidUri() {
26+
NetworkModuleService.validateURI("no URI at all");
27+
}
28+
29+
@Test(expected = IllegalArgumentException.class)
30+
public void failWithPathOnTcpUri() {
31+
NetworkModuleService.validateURI("tcp://host_literal:1883/somePath");
32+
}
33+
34+
@Test(expected = IllegalArgumentException.class)
35+
public void failWithPathOnSslUri() {
36+
NetworkModuleService.validateURI("ssl://host_literal:1883/somePath");
37+
}
38+
39+
@Test(expected = IllegalArgumentException.class)
40+
public void failWithUnsuppurtedSchemeUri() {
41+
NetworkModuleService.validateURI("unknown://host_literal:1883");
42+
}
43+
44+
/**
45+
* Test for URI parsing with '_' in hostname.
46+
*/
47+
@Test
48+
public void testApplyRFC3986AuthorityPatch() throws URISyntaxException {
49+
URI uri = new URI("tcp://user:password@some_host:666/some_path");
50+
/*
51+
* If the following asserts trigger, then the patch may be no longer required, as Java URI class does the
52+
* RFC3986 parsing itself.
53+
*/
54+
assertNull("patch no longer necessary?", uri.getUserInfo());
55+
assertNull("patch no longer necessary?", uri.getHost());
56+
assertEquals("patch no longer necessary?", -1, uri.getPort());
57+
58+
NetworkModuleService.applyRFC3986AuthorityPatch(uri);
59+
60+
assertEquals("wrong user info", "user:password", uri.getUserInfo());
61+
assertEquals("wrong hostname", "some_host", uri.getHost());
62+
assertEquals("wrong port", 666, uri.getPort());
63+
}
64+
65+
@Test
66+
public void testCreateInstance() throws MqttException {
67+
String brokerUri = "tcp://localhost:666";
68+
MqttConnectOptions options = new MqttConnectOptions();
69+
int conTimeout = 234;
70+
options.setConnectionTimeout(conTimeout);
71+
String clientId = "";
72+
73+
NetworkModule result = NetworkModuleService.createInstance(brokerUri, options, clientId);
74+
75+
assertTrue(result instanceof TCPNetworkModule);
76+
assertEquals(brokerUri, result.getServerURI());
77+
}
78+
}

org.eclipse.paho.client.mqttv3/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ Export-Package: org.eclipse.paho.client.mqttv3;version="1.2.1.qualifier",
1010
org.eclipse.paho.client.mqttv3.util;version="1.2.1.qualifier"
1111
Bundle-Vendor: %bundle.provider
1212
Bundle-ActivationPolicy: lazy
13-
Bundle-RequiredExecutionEnvironment: J2SE-1.4
13+
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
1414
Import-Package: javax.net;resolution:=optional,
1515
javax.net.ssl;resolution:=optional

org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttAsyncClient.java

Lines changed: 3 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121

2222
package org.eclipse.paho.client.mqttv3;
2323

24-
import java.lang.reflect.Field;
25-
import java.net.URI;
26-
import java.net.URISyntaxException;
2724
import java.util.Hashtable;
2825
import java.util.Properties;
2926
import java.util.Timer;
@@ -32,18 +29,12 @@
3229
import java.util.concurrent.ScheduledExecutorService;
3330

3431
import javax.net.SocketFactory;
35-
import javax.net.ssl.SSLSocketFactory;
36-
3732
import org.eclipse.paho.client.mqttv3.internal.ClientComms;
3833
import org.eclipse.paho.client.mqttv3.internal.ConnectActionListener;
3934
import org.eclipse.paho.client.mqttv3.internal.DisconnectedMessageBuffer;
4035
import org.eclipse.paho.client.mqttv3.internal.ExceptionHelper;
4136
import org.eclipse.paho.client.mqttv3.internal.NetworkModule;
42-
import org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule;
43-
import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule;
44-
import org.eclipse.paho.client.mqttv3.internal.security.SSLSocketFactoryFactory;
45-
import org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketNetworkModule;
46-
import org.eclipse.paho.client.mqttv3.internal.websocket.WebSocketSecureNetworkModule;
37+
import org.eclipse.paho.client.mqttv3.internal.NetworkModuleService;
4738
import org.eclipse.paho.client.mqttv3.internal.wire.MqttDisconnect;
4839
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
4940
import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe;
@@ -460,7 +451,7 @@ public MqttAsyncClient(String serverURI, String clientId, MqttClientPersistence
460451
throw new IllegalArgumentException("ClientId longer than 65535 characters");
461452
}
462453

463-
MqttConnectOptions.validateURI(serverURI);
454+
NetworkModuleService.validateURI(serverURI);
464455

465456
this.serverURI = serverURI;
466457
this.clientId = clientId;
@@ -547,129 +538,7 @@ private NetworkModule createNetworkModule(String address, MqttConnectOptions opt
547538
// @TRACE 115=URI={0}
548539
log.fine(CLASS_NAME,methodName, "115", new Object[] {address});
549540

550-
NetworkModule netModule;
551-
SocketFactory factory = options.getSocketFactory();
552-
553-
int serverURIType = MqttConnectOptions.validateURI(address);
554-
555-
URI uri;
556-
try {
557-
uri = new URI(address);
558-
// If the returned uri contains no host and the address contains underscores,
559-
// then it's likely that Java did not parse the URI
560-
if(uri.getHost() == null && address.contains("_")){
561-
try {
562-
final Field hostField = URI.class.getDeclaredField("host");
563-
hostField.setAccessible(true);
564-
// Get everything after the scheme://
565-
String shortAddress = address.substring(uri.getScheme().length() + 3);
566-
hostField.set(uri, getHostName(shortAddress));
567-
568-
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
569-
throw ExceptionHelper.createMqttException(e.getCause());
570-
}
571-
572-
}
573-
} catch (URISyntaxException e) {
574-
throw new IllegalArgumentException("Malformed URI: " + address + ", " + e.getMessage());
575-
}
576-
577-
String host = uri.getHost();
578-
int port = uri.getPort(); // -1 if not defined
579-
580-
switch (serverURIType) {
581-
case MqttConnectOptions.URI_TYPE_TCP :
582-
if (port == -1) {
583-
port = 1883;
584-
}
585-
if (factory == null) {
586-
factory = SocketFactory.getDefault();
587-
}
588-
else if (factory instanceof SSLSocketFactory) {
589-
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
590-
}
591-
netModule = new TCPNetworkModule(factory, host, port, clientId);
592-
((TCPNetworkModule)netModule).setConnectTimeout(options.getConnectionTimeout());
593-
break;
594-
case MqttConnectOptions.URI_TYPE_SSL:
595-
if (port == -1) {
596-
port = 8883;
597-
}
598-
SSLSocketFactoryFactory factoryFactory = null;
599-
if (factory == null) {
600-
// try {
601-
factoryFactory = new SSLSocketFactoryFactory();
602-
Properties sslClientProps = options.getSSLProperties();
603-
if (null != sslClientProps)
604-
factoryFactory.initialize(sslClientProps, null);
605-
factory = factoryFactory.createSocketFactory(null);
606-
// }
607-
// catch (MqttDirectException ex) {
608-
// throw ExceptionHelper.createMqttException(ex.getCause());
609-
// }
610-
}
611-
else if ((factory instanceof SSLSocketFactory) == false) {
612-
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
613-
}
614-
615-
// Create the network module...
616-
netModule = new SSLNetworkModule((SSLSocketFactory) factory, host, port, clientId);
617-
((SSLNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout());
618-
((SSLNetworkModule)netModule).setSSLHostnameVerifier(options.getSSLHostnameVerifier());
619-
// Ciphers suites need to be set, if they are available
620-
if (factoryFactory != null) {
621-
String[] enabledCiphers = factoryFactory.getEnabledCipherSuites(null);
622-
if (enabledCiphers != null) {
623-
((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
624-
}
625-
}
626-
break;
627-
case MqttConnectOptions.URI_TYPE_WS:
628-
if (port == -1) {
629-
port = 80;
630-
}
631-
if (factory == null) {
632-
factory = SocketFactory.getDefault();
633-
}
634-
else if (factory instanceof SSLSocketFactory) {
635-
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
636-
}
637-
netModule = new WebSocketNetworkModule(factory, address, host, port, clientId);
638-
((WebSocketNetworkModule)netModule).setConnectTimeout(options.getConnectionTimeout());
639-
break;
640-
case MqttConnectOptions.URI_TYPE_WSS:
641-
if (port == -1) {
642-
port = 443;
643-
}
644-
SSLSocketFactoryFactory wSSFactoryFactory = null;
645-
if (factory == null) {
646-
wSSFactoryFactory = new SSLSocketFactoryFactory();
647-
Properties sslClientProps = options.getSSLProperties();
648-
if (null != sslClientProps)
649-
wSSFactoryFactory.initialize(sslClientProps, null);
650-
factory = wSSFactoryFactory.createSocketFactory(null);
651-
652-
}
653-
else if ((factory instanceof SSLSocketFactory) == false) {
654-
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_SOCKET_FACTORY_MISMATCH);
655-
}
656-
657-
// Create the network module...
658-
netModule = new WebSocketSecureNetworkModule((SSLSocketFactory) factory, address, host, port, clientId);
659-
((WebSocketSecureNetworkModule)netModule).setSSLhandshakeTimeout(options.getConnectionTimeout());
660-
// Ciphers suites need to be set, if they are available
661-
if (wSSFactoryFactory != null) {
662-
String[] enabledCiphers = wSSFactoryFactory.getEnabledCipherSuites(null);
663-
if (enabledCiphers != null) {
664-
((SSLNetworkModule) netModule).setEnabledCiphers(enabledCiphers);
665-
}
666-
}
667-
break;
668-
default:
669-
// This shouldn't happen, as long as validateURI() has been called.
670-
log.fine(CLASS_NAME,methodName, "119", new Object[] {address});
671-
netModule = null;
672-
}
541+
NetworkModule netModule = NetworkModuleService.createInstance(address, options, clientId);
673542
return netModule;
674543
}
675544

org.eclipse.paho.client.mqttv3/src/main/java/org/eclipse/paho/client/mqttv3/MqttConnectOptions.java

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,9 @@
2121

2222
import javax.net.SocketFactory;
2323
import javax.net.ssl.HostnameVerifier;
24-
24+
import org.eclipse.paho.client.mqttv3.internal.NetworkModuleService;
2525
import org.eclipse.paho.client.mqttv3.util.Debug;
2626

27-
import java.net.URI;
28-
import java.net.URISyntaxException;
29-
3027
/**
3128
* Holds the set of options that control how the client connects to a server.
3229
*/
@@ -60,12 +57,6 @@ public class MqttConnectOptions {
6057
*/
6158
public static final int MQTT_VERSION_3_1_1 = 4;
6259

63-
protected static final int URI_TYPE_TCP = 0;
64-
protected static final int URI_TYPE_SSL = 1;
65-
protected static final int URI_TYPE_LOCAL = 2;
66-
protected static final int URI_TYPE_WS = 3;
67-
protected static final int URI_TYPE_WSS = 4;
68-
6960
private int keepAliveInterval = KEEP_ALIVE_INTERVAL_DEFAULT;
7061
private int maxInflight = MAX_INFLIGHT_DEFAULT;
7162
private String willDestination = null;
@@ -510,51 +501,13 @@ public String[] getServerURIs() {
510501
* durable subscriptions are not valid. The cleansession flag must be set to true if the
511502
* hunt list mode is used</p></li>
512503
* </ol>
513-
* @param array of serverURIs
504+
* @param serverURIs to be used by the client
514505
*/
515-
public void setServerURIs(String[] array) {
516-
for (int i = 0; i < array.length; i++) {
517-
validateURI(array[i]);
518-
}
519-
this.serverURIs = array.clone();
520-
}
521-
522-
/**
523-
* Validate a URI
524-
* @param srvURI The Server URI
525-
* @return the URI type
526-
*/
527-
public static int validateURI(String srvURI) {
528-
try {
529-
URI vURI = new URI(srvURI);
530-
if ("ws".equals(vURI.getScheme())){
531-
return URI_TYPE_WS;
532-
}
533-
else if ("wss".equals(vURI.getScheme())) {
534-
return URI_TYPE_WSS;
535-
}
536-
537-
if ((vURI.getPath() == null) || vURI.getPath().isEmpty()) {
538-
// No op path must be empty
539-
}
540-
else {
541-
throw new IllegalArgumentException(srvURI);
542-
}
543-
if ("tcp".equals(vURI.getScheme())) {
544-
return URI_TYPE_TCP;
545-
}
546-
else if ("ssl".equals(vURI.getScheme())) {
547-
return URI_TYPE_SSL;
548-
}
549-
else if ("local".equals(vURI.getScheme())) {
550-
return URI_TYPE_LOCAL;
551-
}
552-
else {
553-
throw new IllegalArgumentException(srvURI);
554-
}
555-
} catch (URISyntaxException ex) {
556-
throw new IllegalArgumentException(srvURI);
506+
public void setServerURIs(String[] serverURIs) {
507+
for (String serverURI:serverURIs) {
508+
NetworkModuleService.validateURI(serverURI);
557509
}
510+
this.serverURIs = serverURIs.clone();
558511
}
559512

560513
/**

0 commit comments

Comments
 (0)