Skip to content

Commit e41e674

Browse files
lsiepeldigitaldan
authored andcommitted
[xmpp] Improve reconnection logic (openhab#14397)
* Add null annotations * Introduce re-connect logic Signed-off-by: lsiepel <[email protected]> Signed-off-by: Leo Siepel <[email protected]>
1 parent c1d0dc6 commit e41e674

13 files changed

+253
-74
lines changed

bundles/org.openhab.binding.xmppclient/README.md

+12-11
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,23 @@ Bridge xmppclient:xmppBridge:xmpp "XMPP Client" [ host="xmpp.example.com", port=
2626

2727
**xmppBridge** parameters:
2828

29-
| Name | Label | Description | Required | Default value |
30-
|----------|--------------------|-------------------------------------------|-----------|-----------------------|
31-
| username | Username | The XMPP username (left part of JID) | true | - |
32-
| domain | Domain | The XMPP domain name (right part of JID) | true | - |
33-
| password | Password | The XMPP user password | true | - |
34-
| host | Server Hostname/IP | The IP/Hostname of the XMPP server | false | as "domain" parameter |
35-
| port | XMPP server Port | The typical port is 5222 | false | 5222 |
29+
| Name | Label | Description | Required | Default value |
30+
|--------------|--------------------|--------------------------------------------------------------------|----------|-----------------------|
31+
| username | Username | The XMPP username (left part of JID) | true | - |
32+
| domain | Domain | The XMPP domain name (right part of JID) | true | - |
33+
| password | Password | The XMPP user password | true | - |
34+
| host | Server Hostname/IP | The IP/Hostname of the XMPP server | false | as "domain" parameter |
35+
| port | XMPP server Port | The typical port is 5222 | false | 5222 |
36+
| securityMode | Security Mode | Sets the TLS security mode: `required`, `ifpossible` or `disabled` | false | `required` |
3637

3738
## Channels
3839

3940
**publishTrigger** parameters:
4041

41-
| Name | Label | Description | Required |
42-
|-----------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
43-
| payload | Payload condition | An optional condition on the value | false |
44-
| separator | Separator character | The trigger channel payload usually only contains the received text. If you define a separator character, for example '#', the sender UID and received text will be in the trigger channel payload. For example: [email protected]#My Message Text | false |
42+
| Name | Label | Description | Required |
43+
|-----------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
44+
| payload | Payload condition | An optional condition on the value | false |
45+
| separator | Separator character | The trigger channel payload usually only contains the received text. If you define a separator character, for example '#', the sender UID and received text will be in the trigger channel payload. For example: [email protected]#My Message Text | false |
4546

4647
## Example Rules
4748

bundles/org.openhab.binding.xmppclient/src/main/java/org/openhab/binding/xmppclient/internal/XMPPClientBindingConstants.java

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package org.openhab.binding.xmppclient.internal;
1414

15+
import org.eclipse.jdt.annotation.NonNullByDefault;
1516
import org.openhab.core.thing.ThingTypeUID;
1617

1718
/**
@@ -20,6 +21,7 @@
2021
*
2122
* @author Pavel Gololobov - Initial contribution
2223
*/
24+
@NonNullByDefault
2325
public class XMPPClientBindingConstants {
2426
private static final String BINDING_ID = "xmppclient";
2527

bundles/org.openhab.binding.xmppclient/src/main/java/org/openhab/binding/xmppclient/internal/XMPPClientHandlerFactory.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
import java.util.Set;
1616

17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.eclipse.jdt.annotation.Nullable;
1719
import org.openhab.binding.xmppclient.internal.handler.XMPPClientHandler;
1820
import org.openhab.core.thing.Bridge;
1921
import org.openhab.core.thing.Thing;
@@ -29,6 +31,7 @@
2931
*
3032
* @author Pavel Gololobov - Initial contribution
3133
*/
34+
@NonNullByDefault
3235
@Component(configurationPid = "binding.xmppclient", service = ThingHandlerFactory.class)
3336
public class XMPPClientHandlerFactory extends BaseThingHandlerFactory {
3437
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set
@@ -40,7 +43,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
4043
}
4144

4245
@Override
43-
protected ThingHandler createHandler(Thing thing) {
46+
protected @Nullable ThingHandler createHandler(Thing thing) {
4447
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
4548

4649
if (thingTypeUID.equals(XMPPClientBindingConstants.BRIDGE_TYPE_XMPP)) {

bundles/org.openhab.binding.xmppclient/src/main/java/org/openhab/binding/xmppclient/internal/action/XMPPActions.java

+5-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import org.eclipse.jdt.annotation.NonNullByDefault;
1616
import org.eclipse.jdt.annotation.Nullable;
17-
import org.openhab.binding.xmppclient.internal.XMPPClient;
17+
import org.openhab.binding.xmppclient.internal.client.XMPPClient;
1818
import org.openhab.binding.xmppclient.internal.handler.XMPPClientHandler;
1919
import org.openhab.core.automation.annotation.ActionInput;
2020
import org.openhab.core.automation.annotation.RuleAction;
@@ -35,7 +35,7 @@
3535
@ThingActionsScope(name = "xmppclient")
3636
@NonNullByDefault
3737
public class XMPPActions implements ThingActions {
38-
private static final Logger logger = LoggerFactory.getLogger(XMPPActions.class);
38+
private final Logger logger = LoggerFactory.getLogger(XMPPActions.class);
3939
private @Nullable XMPPClientHandler handler;
4040

4141
@Override
@@ -58,12 +58,8 @@ public void publishXMPP(@ActionInput(name = "to", label = "To", description = "S
5858
}
5959

6060
XMPPClient connection = clientHandler.getXMPPClient();
61-
if (connection == null) {
62-
logger.warn("XMPP ThingHandler connection is null");
63-
return;
64-
}
65-
if ((to == null) || (text == null)) {
66-
logger.info("Skipping XMPP messaging to {} value {}", to, text);
61+
if (to == null || text == null) {
62+
logger.warn("Skipping XMPP messaging to {} value {}", to, text);
6763
return;
6864
}
6965
connection.sendMessage(to, text);
@@ -80,11 +76,7 @@ public void publishXMPPImageByHTTP(
8076
}
8177

8278
XMPPClient connection = clientHandler.getXMPPClient();
83-
if (connection == null) {
84-
logger.warn("XMPP ThingHandler connection is null");
85-
return;
86-
}
87-
if ((to == null) || (filename == null)) {
79+
if (to == null || filename == null) {
8880
logger.warn("Skipping XMPP messaging to {} value {}", to, filename);
8981
return;
9082
}

bundles/org.openhab.binding.xmppclient/src/main/java/org/openhab/binding/xmppclient/internal/XMPPClient.java bundles/org.openhab.binding.xmppclient/src/main/java/org/openhab/binding/xmppclient/internal/client/XMPPClient.java

+62-36
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@
1010
*
1111
* SPDX-License-Identifier: EPL-2.0
1212
*/
13-
package org.openhab.binding.xmppclient.internal;
13+
package org.openhab.binding.xmppclient.internal.client;
1414

1515
import java.io.File;
1616
import java.io.IOException;
1717
import java.net.URL;
1818
import java.util.HashSet;
19+
import java.util.Objects;
1920
import java.util.Set;
2021

22+
import org.eclipse.jdt.annotation.NonNullByDefault;
23+
import org.eclipse.jdt.annotation.Nullable;
2124
import org.jivesoftware.smack.AbstractXMPPConnection;
25+
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
2226
import org.jivesoftware.smack.ConnectionListener;
2327
import org.jivesoftware.smack.ReconnectionManager;
2428
import org.jivesoftware.smack.SmackException;
@@ -44,13 +48,20 @@
4448
* The {@link XMPPClient} is lib for handling XMPP connection and messaging
4549
*
4650
* @author Pavel Gololobov - Initial contribution
51+
* @author Leo Siepel - Add reconnection logic
4752
*/
53+
@NonNullByDefault
4854
public class XMPPClient implements IncomingChatMessageListener, ConnectionListener {
4955
private final Logger logger = LoggerFactory.getLogger(XMPPClient.class);
50-
private AbstractXMPPConnection connection;
51-
private ChatManager chatManager;
52-
private HttpFileUploadManager httpFileUploadManager;
56+
private @Nullable AbstractXMPPConnection connection;
57+
private @Nullable ChatManager chatManager;
58+
private @Nullable HttpFileUploadManager httpFileUploadManager;
5359
private Set<XMPPClientMessageSubscriber> subscribers = new HashSet<>();
60+
private final XMPPClientEventlistener eventListener;
61+
62+
public XMPPClient(XMPPClientEventlistener eventListener) {
63+
this.eventListener = eventListener;
64+
}
5465

5566
public void subscribe(XMPPClientMessageSubscriber channel) {
5667
logger.debug("Channel {} subscribed", channel.getName());
@@ -62,62 +73,75 @@ public void unsubscribe(XMPPClientMessageSubscriber channel) {
6273
subscribers.remove(channel);
6374
}
6475

65-
public void connect(String host, Integer port, String login, String domain, String password)
66-
throws XMPPException, SmackException, IOException {
76+
public void connect(String host, Integer port, String login, String domain, String password,
77+
SecurityMode securityMode) throws XMPPClientConfigException, XMPPClientException {
6778
disconnect();
6879
String serverHost = domain;
69-
if ((host != null) && !host.isEmpty()) {
80+
if (!host.isBlank()) {
7081
serverHost = host;
7182
}
7283

73-
XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder() //
74-
.setHost(serverHost) //
75-
.setPort(port) //
76-
.setUsernameAndPassword(login, password) //
77-
.setXmppDomain(domain) //
78-
.build();
84+
XMPPTCPConnectionConfiguration config;
85+
try {
86+
config = XMPPTCPConnectionConfiguration.builder() //
87+
.setHost(serverHost) //
88+
.setPort(port) //
89+
.setUsernameAndPassword(login, password) //
90+
.setXmppDomain(domain) //
91+
.setSecurityMode(securityMode)//
92+
.build();
93+
} catch (XmppStringprepException e) {
94+
throw new XMPPClientConfigException(Objects.requireNonNullElse(e.getMessage(), "Unknown error message"));
95+
}
7996

80-
connection = new XMPPTCPConnection(config);
81-
connection.addConnectionListener(this);
97+
AbstractXMPPConnection connectionLocal = new XMPPTCPConnection(config);
98+
connection = connectionLocal;
99+
connectionLocal.addConnectionListener(this);
82100

83-
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connection);
101+
ReconnectionManager reconnectionManager = ReconnectionManager.getInstanceFor(connectionLocal);
84102
reconnectionManager.enableAutomaticReconnection();
85103

86104
Identity identity = new Identity("client", "openHAB", "bot");
87105
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
88106
sdm.setIdentity(identity);
89107

90108
try {
91-
connection.connect().login();
92-
} catch (InterruptedException ex) {
109+
connectionLocal.connect().login();
110+
} catch (InterruptedException | XMPPException | SmackException | IOException e) {
111+
throw new XMPPClientException(Objects.requireNonNullElse(e.getMessage(), "Unknown error message"),
112+
e.getCause());
93113
}
94114

95-
chatManager = ChatManager.getInstanceFor(connection);
115+
ChatManager chatManager = ChatManager.getInstanceFor(connection);
96116
chatManager.addIncomingListener(this);
117+
this.chatManager = chatManager;
97118
httpFileUploadManager = HttpFileUploadManager.getInstanceFor(connection);
98119
}
99120

100121
public void disconnect() {
122+
AbstractXMPPConnection connection = this.connection;
101123
if (connection != null) {
102124
connection.disconnect();
103125
}
104126
}
105127

106128
public void sendMessage(String to, String message) {
107129
if (connection == null) {
108-
logger.warn("XMPP connection is null");
130+
eventListener.onErrorEvent("XMPP connection is null");
109131
return;
110132
}
133+
134+
ChatManager chatManager = this.chatManager;
111135
if (chatManager == null) {
112-
logger.warn("XMPP chatManager is null");
136+
eventListener.onErrorEvent("XMPP chatManager is null");
113137
return;
114138
}
115139
try {
116140
EntityBareJid jid = JidCreate.entityBareFrom(to);
117141
Chat chat = chatManager.chatWith(jid);
118142
chat.send(message);
119143
} catch (XmppStringprepException | SmackException.NotConnectedException | InterruptedException e) {
120-
logger.info("XMPP message sending error", e);
144+
logger.warn("XMPP message sending error", e);
121145
}
122146
}
123147

@@ -126,12 +150,13 @@ public void sendImageByHTTP(String to, String filename) {
126150
logger.warn("XMPP connection is null");
127151
return;
128152
}
129-
if (httpFileUploadManager == null) {
153+
HttpFileUploadManager httpFileUploadManagerLocal = httpFileUploadManager;
154+
if (httpFileUploadManagerLocal == null) {
130155
logger.warn("XMPP httpFileUploadManager is null");
131156
return;
132157
}
133158
try {
134-
URL u = httpFileUploadManager.uploadFile(new File(filename));
159+
URL u = httpFileUploadManagerLocal.uploadFile(new File(filename));
135160
// Use Stanza oob
136161
this.sendMessage(to, u.toString());
137162
} catch (XMPPException.XMPPErrorException | SmackException | InterruptedException | IOException e) {
@@ -140,7 +165,12 @@ public void sendImageByHTTP(String to, String filename) {
140165
}
141166

142167
@Override
143-
public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
168+
public void newIncomingMessage(@Nullable EntityBareJid from, @Nullable Message message, @Nullable Chat chat) {
169+
if (from == null || message == null || chat == null) {
170+
logger.debug("newIncomingMessage with atleast one null argument, should not happen");
171+
return;
172+
}
173+
144174
logger.debug("XMPP {} says {}", from.asBareJid().toString(), message.getBody());
145175
for (XMPPClientMessageSubscriber subscriber : subscribers) {
146176
logger.debug("Push to subscriber {}", subscriber.getName());
@@ -149,30 +179,26 @@ public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) {
149179
}
150180

151181
@Override
152-
public void connected(XMPPConnection connection) {
182+
public void connected(@Nullable XMPPConnection connection) {
153183
logger.debug("Connected to XMPP server.");
184+
eventListener.onAllOk();
154185
}
155186

156187
@Override
157-
public void authenticated(XMPPConnection connection, boolean resumed) {
188+
public void authenticated(@Nullable XMPPConnection connection, boolean resumed) {
158189
logger.debug("Authenticated to XMPP server.");
190+
eventListener.onAllOk();
159191
}
160192

161193
@Override
162194
public void connectionClosed() {
163195
logger.debug("XMPP connection was closed.");
196+
eventListener.onErrorEvent("XMPP connection was closed.");
164197
}
165198

166199
@Override
167-
public void connectionClosedOnError(Exception e) {
200+
public void connectionClosedOnError(@Nullable Exception e) {
168201
logger.debug("Connection to XMPP server was lost.");
169-
if (connection != null) {
170-
connection.disconnect();
171-
try {
172-
connection.connect().login();
173-
} catch (SmackException | IOException | XMPPException | InterruptedException ex) {
174-
logger.info("XMPP connection error", ex);
175-
}
176-
}
202+
eventListener.onErrorEvent("XMPP connection was closed.");
177203
}
178204
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright (c) 2010-2024 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.xmppclient.internal.client;
14+
15+
import org.eclipse.jdt.annotation.NonNullByDefault;
16+
import org.eclipse.jdt.annotation.Nullable;
17+
18+
/**
19+
* The {@link XMPPClientConfigException} represents a binding specific {@link Exception}.
20+
*
21+
* @author Leo Siepel - Initial contribution
22+
*/
23+
24+
@NonNullByDefault
25+
public class XMPPClientConfigException extends Exception {
26+
27+
private static final long serialVersionUID = 1L;
28+
29+
public XMPPClientConfigException(String message) {
30+
super(message);
31+
}
32+
33+
public XMPPClientConfigException(String message, @Nullable Throwable cause) {
34+
super(message, cause);
35+
}
36+
37+
public XMPPClientConfigException(@Nullable Throwable cause) {
38+
super(cause);
39+
}
40+
}

0 commit comments

Comments
 (0)