*/
package org.openhab.io.openhabcloud;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.model.script.engine.action.ActionDoc;
import org.openhab.io.openhabcloud.internal.CloudService;
import org.slf4j.Logger;
*
* @author Victor Belov - Initial contribution
* @author Kai Kreuzer - migrated code to ESH APIs
- *
*/
-
+@NonNullByDefault
public class NotificationAction {
private static final Logger logger = LoggerFactory.getLogger(NotificationAction.class);
- public static CloudService cloudService = null;
+ public static @Nullable CloudService cloudService;
/**
* Sends a simple push notification to mobile devices of user
*
*/
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
- public static void sendNotification(String userId, String message, String icon, String severity) {
+ public static void sendNotification(String userId, String message, @Nullable String icon,
+ @Nullable String severity) {
logger.debug("sending notification '{}' to user {}", message, userId);
if (cloudService != null) {
cloudService.sendNotification(userId, message, icon, severity);
*
*/
@ActionDoc(text = "Sends a log notification which is shown in notifications log to all account users")
- public static void sendLogNotification(String message, String icon, String severity) {
+ public static void sendLogNotification(String message, @Nullable String icon, @Nullable String severity) {
logger.debug("sending log notification '{}'", message);
if (cloudService != null) {
cloudService.sendLogNotification(message, icon, severity);
*
*/
@ActionDoc(text = "Sends a push notification to mobile devices of user with userId")
- public static void sendBroadcastNotification(String message, String icon, String severity) {
+ public static void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
logger.debug("sending broadcast notification '{}' to all users", message);
if (cloudService != null) {
cloudService.sendBroadcastNotification(message, icon, severity);
import java.net.URL;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Request.FailureListener;
*
* @author Victor Belov - Initial contribution
* @author Kai Kreuzer - migrated code to new Jetty client and ESH APIs
- *
*/
-
public class CloudClient {
/*
* Logger for this class
*/
- private Logger logger = LoggerFactory.getLogger(CloudClient.class);
+ private final Logger logger = LoggerFactory.getLogger(CloudClient.class);
/*
* This variable holds base URL for the openHAB Cloud connections
private final HttpClient jettyClient;
/*
- * This hashmap holds HTTP requests to local openHAB which are currently running
+ * This map holds HTTP requests to local openHAB which are currently running
*/
- private Map<Integer, Request> runningRequests;
+ private final Map<Integer, Request> runningRequests = new ConcurrentHashMap<>();
/*
* This variable indicates if connection to the openHAB Cloud is currently in an established state
this.localBaseUrl = localBaseUrl;
this.remoteAccessEnabled = remoteAccessEnabled;
this.exposedItems = exposedItems;
- runningRequests = new HashMap<>();
this.jettyClient = httpClient;
}
logger.trace("Transport.EVENT_REQUEST_HEADERS");
@SuppressWarnings("unchecked")
Map<String, List<String>> headers = (Map<String, List<String>>) args[0];
- headers.put("uuid", Arrays.asList(uuid));
- headers.put("secret", Arrays.asList(secret));
- headers.put("openhabversion", Arrays.asList(OpenHAB.getVersion()));
- headers.put("clientversion", Arrays.asList(CloudService.clientVersion));
- headers.put("remoteaccess", Arrays.asList(((Boolean) remoteAccessEnabled).toString()));
+ headers.put("uuid", List.of(uuid));
+ headers.put("secret", List.of(secret));
+ headers.put("openhabversion", List.of(OpenHAB.getVersion()));
+ headers.put("clientversion", List.of(CloudService.clientVersion));
+ headers.put("remoteaccess", List.of(((Boolean) remoteAccessEnabled).toString()));
}
});
}
this.localBaseUrl);
isConnected = false;
// And clean up the list of running requests
- if (runningRequests != null) {
- runningRequests.clear();
- }
+ runningRequests.clear();
}
/**
JSONObject requestQueryJson = data.getJSONObject("query");
// Create URI builder with base request URI of openHAB and path from request
String newPath = URIUtil.addPaths(localBaseUrl, requestPath);
- @SuppressWarnings("unchecked")
Iterator<String> queryIterator = requestQueryJson.keys();
// Add query parameters to URI builder, if any
newPath += "?";
}
private void setRequestHeaders(Request request, JSONObject requestHeadersJson) {
- @SuppressWarnings("unchecked")
Iterator<String> headersIterator = requestHeadersJson.keys();
// Convert JSONObject of headers into Header ArrayList
while (headersIterator.hasNext()) {
int requestId = data.getInt("id");
logger.debug("Received cancel for request {}", requestId);
// Find and abort running request
- if (runningRequests.containsKey(requestId)) {
- Request request = runningRequests.get(requestId);
+ Request request = runningRequests.get(requestId);
+ if (request != null) {
request.abort(new InterruptedException());
runningRequests.remove(requestId);
}
* @param message notification message text
* @param icon name of the icon for this notification
* @param severity severity name for this notification
- *
*/
- public void sendNotification(String userId, String message, String icon, String severity) {
+ public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity) {
if (isConnected()) {
JSONObject notificationMessage = new JSONObject();
try {
* @param message notification message text
* @param icon name of the icon for this notification
* @param severity severity name for this notification
- *
*/
- public void sendLogNotification(String message, String icon, String severity) {
+ public void sendLogNotification(String message, @Nullable String icon, @Nullable String severity) {
if (isConnected()) {
JSONObject notificationMessage = new JSONObject();
try {
* @param message notification message text
* @param icon name of the icon for this notification
* @param severity severity name for this notification
- *
*/
- public void sendBroadcastNotification(String message, String icon, String severity) {
+ public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
if (isConnected()) {
JSONObject notificationMessage = new JSONObject();
try {
} catch (JSONException e) {
logger.debug("{}", e.getMessage());
}
- } else {
- // We should not send headers for the second time...
}
}
}
package org.openhab.io.openhabcloud.internal;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.security.SecureRandom;
import java.util.Arrays;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.RandomStringUtils;
-import org.apache.commons.lang.StringUtils;
+import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.OpenHAB;
import org.openhab.core.config.core.ConfigurableService;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final int DEFAULT_LOCAL_OPENHAB_MAX_CONCURRENT_REQUESTS = 200;
private static final int DEFAULT_LOCAL_OPENHAB_REQUEST_TIMEOUT = 30000;
private static final String HTTPCLIENT_NAME = "openhabcloud";
+ private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ private static final SecureRandom SR = new SecureRandom();
- private Logger logger = LoggerFactory.getLogger(CloudService.class);
+ private final Logger logger = LoggerFactory.getLogger(CloudService.class);
public static String clientVersion = null;
private CloudClient cloudClient;
private String cloudBaseUrl = null;
- private HttpClient httpClient;
- protected ItemRegistry itemRegistry = null;
- protected EventPublisher eventPublisher = null;
+ private final HttpClient httpClient;
+ protected final ItemRegistry itemRegistry;
+ protected final EventPublisher eventPublisher;
private boolean remoteAccessEnabled = true;
private Set<String> exposedItems = null;
private int localPort;
- public CloudService() {
+ @Activate
+ public CloudService(final @Reference HttpClientFactory httpClientFactory,
+ final @Reference ItemRegistry itemRegistry, final @Reference EventPublisher eventPublisher) {
+ this.httpClient = httpClientFactory.createHttpClient(HTTPCLIENT_NAME);
+ this.httpClient.setStopTimeout(0);
+ this.httpClient.setMaxConnectionsPerDestination(DEFAULT_LOCAL_OPENHAB_MAX_CONCURRENT_REQUESTS);
+ this.httpClient.setConnectTimeout(DEFAULT_LOCAL_OPENHAB_REQUEST_TIMEOUT);
+ this.httpClient.setFollowRedirects(false);
+
+ this.itemRegistry = itemRegistry;
+ this.eventPublisher = eventPublisher;
}
/**
* @param icon the {@link String} containing a name of the icon to be used with this notification
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
*/
- public void sendNotification(String userId, String message, String icon, String severity) {
+ public void sendNotification(String userId, String message, @Nullable String icon, @Nullable String severity) {
logger.debug("Sending message '{}' to user id {}", message, userId);
cloudClient.sendNotification(userId, message, icon, severity);
}
* @param icon the {@link String} containing a name of the icon to be used with this notification
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
*/
- public void sendLogNotification(String message, String icon, String severity) {
+ public void sendLogNotification(String message, @Nullable String icon, @Nullable String severity) {
logger.debug("Sending log message '{}'", message);
cloudClient.sendLogNotification(message, icon, severity);
}
* @param icon the {@link String} containing a name of the icon to be used with this notification
* @param severity the {@link String} containing severity (good, info, warning, error) of notification
*/
- public void sendBroadcastNotification(String message, String icon, String severity) {
+ public void sendBroadcastNotification(String message, @Nullable String icon, @Nullable String severity) {
logger.debug("Sending broadcast message '{}' to all users", message);
cloudClient.sendBroadcastNotification(message, icon, severity);
}
+ private String substringBefore(String str, String separator) {
+ int index = str.indexOf(separator);
+ return index == -1 ? str : str.substring(0, index);
+ }
+
@Activate
protected void activate(BundleContext context, Map<String, ?> config) {
- clientVersion = StringUtils.substringBefore(context.getBundle().getVersion().toString(), ".qualifier");
+ clientVersion = substringBefore(context.getBundle().getVersion().toString(), ".qualifier");
localPort = HttpServiceUtil.getHttpServicePort(context);
if (localPort == -1) {
logger.warn("openHAB Cloud connector not started, since no local HTTP port could be determined");
cloudClient.shutdown();
}
- httpClient.setMaxConnectionsPerDestination(DEFAULT_LOCAL_OPENHAB_MAX_CONCURRENT_REQUESTS);
- httpClient.setConnectTimeout(DEFAULT_LOCAL_OPENHAB_REQUEST_TIMEOUT);
- httpClient.setFollowRedirects(false);
if (!httpClient.isRunning()) {
try {
httpClient.start();
private String readFirstLine(File file) {
List<String> lines = null;
- try (InputStream fis = new FileInputStream(file)) {
- lines = IOUtils.readLines(fis);
- } catch (IOException ioe) {
+ try {
+ lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
// no exception handling - we just return the empty String
}
- return lines != null && !lines.isEmpty() ? lines.get(0) : "";
+ return lines == null || lines.isEmpty() ? "" : lines.get(0);
}
/**
private void writeFile(File file, String content) {
// create intermediary directories
file.getParentFile().mkdirs();
- try (OutputStream fos = new FileOutputStream(file)) {
- IOUtils.write(content, fos);
+ try {
+ Files.writeString(file.toPath(), content, StandardCharsets.UTF_8);
logger.debug("Created file '{}' with content '{}'", file.getAbsolutePath(), content);
} catch (FileNotFoundException e) {
logger.error("Couldn't create file '{}'.", file.getPath(), e);
}
}
+ private String randomString(int length) {
+ StringBuilder sb = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ sb.append(CHARS.charAt(SR.nextInt(CHARS.length())));
+ }
+ return sb.toString();
+ }
+
/**
* Creates a random secret and writes it to the <code>userdata/openhabcloud</code>
* directory. An existing <code>secret</code> file won't be overwritten.
String newSecretString = "";
if (!file.exists()) {
- newSecretString = RandomStringUtils.randomAlphanumeric(20);
+ newSecretString = randomString(20);
logger.debug("New secret = {}", newSecretString);
writeFile(file, newSecretString);
} else {
@Override
public void sendCommand(String itemName, String commandString) {
try {
- if (itemRegistry != null) {
- Item item = itemRegistry.getItem(itemName);
- Command command = null;
- if (item != null) {
- if (this.eventPublisher != null) {
- if ("toggle".equalsIgnoreCase(commandString)
- && (item instanceof SwitchItem || item instanceof RollershutterItem)) {
- if (OnOffType.ON.equals(item.getStateAs(OnOffType.class))) {
- command = OnOffType.OFF;
- }
- if (OnOffType.OFF.equals(item.getStateAs(OnOffType.class))) {
- command = OnOffType.ON;
- }
- if (UpDownType.UP.equals(item.getStateAs(UpDownType.class))) {
- command = UpDownType.DOWN;
- }
- if (UpDownType.DOWN.equals(item.getStateAs(UpDownType.class))) {
- command = UpDownType.UP;
- }
- } else {
- command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandString);
- }
- if (command != null) {
- logger.debug("Received command '{}' for item '{}'", commandString, itemName);
- this.eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, command));
- } else {
- logger.warn("Received invalid command '{}' for item '{}'", commandString, itemName);
- }
- }
- } else {
- logger.warn("Received command '{}' for non-existent item '{}'", commandString, itemName);
+ Item item = itemRegistry.getItem(itemName);
+ Command command = null;
+ if ("toggle".equalsIgnoreCase(commandString)
+ && (item instanceof SwitchItem || item instanceof RollershutterItem)) {
+ if (OnOffType.ON.equals(item.getStateAs(OnOffType.class))) {
+ command = OnOffType.OFF;
+ }
+ if (OnOffType.OFF.equals(item.getStateAs(OnOffType.class))) {
+ command = OnOffType.ON;
+ }
+ if (UpDownType.UP.equals(item.getStateAs(UpDownType.class))) {
+ command = UpDownType.DOWN;
+ }
+ if (UpDownType.DOWN.equals(item.getStateAs(UpDownType.class))) {
+ command = UpDownType.UP;
}
} else {
- return;
+ command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandString);
+ }
+ if (command != null) {
+ logger.debug("Received command '{}' for item '{}'", commandString, itemName);
+ eventPublisher.post(ItemEventFactory.createCommandEvent(itemName, command));
+ } else {
+ logger.warn("Received invalid command '{}' for item '{}'", commandString, itemName);
}
} catch (ItemNotFoundException e) {
- logger.warn("Received command for a non-existent item '{}'", itemName);
+ logger.warn("Received command '{}' for a non-existent item '{}'", commandString, itemName);
}
}
- @Reference
- protected void setHttpClientFactory(HttpClientFactory httpClientFactory) {
- this.httpClient = httpClientFactory.createHttpClient(HTTPCLIENT_NAME);
- this.httpClient.setStopTimeout(0);
- }
-
- protected void unsetHttpClientFactory(HttpClientFactory httpClientFactory) {
- this.httpClient = null;
- }
-
- @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC)
- public void setItemRegistry(ItemRegistry itemRegistry) {
- this.itemRegistry = itemRegistry;
- }
-
- public void unsetItemRegistry(ItemRegistry itemRegistry) {
- this.itemRegistry = null;
- }
-
- @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
- public void setEventPublisher(EventPublisher eventPublisher) {
- this.eventPublisher = eventPublisher;
- }
-
- public void unsetEventPublisher(EventPublisher eventPublisher) {
- this.eventPublisher = null;
- }
-
@Override
public Set<String> getSubscribedEventTypes() {
- return Collections.singleton(ItemStateEvent.TYPE);
+ return Set.of(ItemStateEvent.TYPE);
}
@Override