Add secure web socket proxy to route ws connections in IoTS cluster

revert-70aa11f8
charitha 6 years ago
parent 4848e271af
commit 82778f06c6

@ -19,13 +19,16 @@
package org.wso2.carbon.device.mgt.analytics.data.publisher;
import org.w3c.dom.Document;
import org.wso2.carbon.databridge.agent.DataPublisher;
import org.wso2.carbon.databridge.agent.exception.DataEndpointConfigurationException;
import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublisherConfigurationException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DataPublisherUtil {
@ -46,4 +49,70 @@ public class DataPublisherUtil {
}
}
public static ArrayList<String> getEndpointGroups(String urlSet) {
ArrayList<String> urlGroups = new ArrayList<>();
Pattern regex = Pattern.compile("\\{.*?\\}");
Matcher regexMatcher = regex.matcher(urlSet);
while(regexMatcher.find()) {
urlGroups.add(regexMatcher.group().replace("{", "").replace("}", ""));
}
if (urlGroups.size() == 0) {
urlGroups.add(urlSet.replace("{", "").replace("}", ""));
}
return urlGroups;
}
public static String[] getEndpoints(String aURLGroup) throws DataEndpointConfigurationException {
boolean isLBURL = false;
boolean isFailOverURL = false;
if (aURLGroup.contains(",")) {
isLBURL = true;
}
if (aURLGroup.contains("|")) {
isFailOverURL = true;
}
if (isLBURL && isFailOverURL) {
throw new DataEndpointConfigurationException("Invalid data endpoints URL set provided : " + aURLGroup +
", a URL group can be configured as failover OR load balancing endpoints.");
} else {
String[] urls;
if (isLBURL) {
urls = aURLGroup.split(",");
} else if (isFailOverURL) {
urls = aURLGroup.split("\\|");
} else {
urls = new String[]{aURLGroup};
}
return urls;
}
}
public static int obtainHashId(String deviceId, int urlGroupsCount) {
byte[] chars = deviceId.getBytes();
int sum = 0;
for (byte b : chars) {
sum += b;
}
return sum % urlGroupsCount;
}
@SuppressWarnings("Duplicates")
public static String replaceProperty(String urlWithPlaceholders) {
String regex = "\\$\\{(.*?)\\}";
Pattern pattern = Pattern.compile(regex);
Matcher matchPattern = pattern.matcher(urlWithPlaceholders);
while (matchPattern.find()) {
String sysPropertyName = matchPattern.group(1);
String sysPropertyValue = System.getProperty(sysPropertyName);
if (sysPropertyValue != null && !sysPropertyName.isEmpty()) {
urlWithPlaceholders = urlWithPlaceholders.replaceAll("\\$\\{(" + sysPropertyName + ")\\}", sysPropertyValue);
}
}
return urlWithPlaceholders;
}
}

@ -18,6 +18,8 @@
*/
package org.wso2.carbon.device.mgt.analytics.data.publisher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.databridge.agent.DataPublisher;
import org.wso2.carbon.databridge.agent.exception.DataEndpointAgentConfigurationException;
import org.wso2.carbon.databridge.agent.exception.DataEndpointAuthenticationException;
@ -26,15 +28,25 @@ import org.wso2.carbon.databridge.agent.exception.DataEndpointException;
import org.wso2.carbon.databridge.commons.exception.TransportException;
import org.wso2.carbon.device.mgt.analytics.data.publisher.config.AnalyticsConfiguration;
import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublisherConfigurationException;
import org.wso2.carbon.device.mgt.analytics.data.publisher.service.EventsPublisherServiceImpl;
import java.util.HashMap;
import java.util.Map;
/**
* This is used to manage data publisher per tenant.
*/
public class DeviceDataPublisher {
private DataPublisher dataPublisher;
private static Log log = LogFactory.getLog(EventsPublisherServiceImpl.class);
private Map<String, DataPublisher> dataPublishers;
private static DeviceDataPublisher deviceDataPublisher;
private DeviceDataPublisher() {
dataPublishers = new HashMap<>();
}
public static DeviceDataPublisher getInstance() {
if (deviceDataPublisher == null) {
synchronized (DeviceDataPublisher.class) {
@ -47,45 +59,52 @@ public class DeviceDataPublisher {
}
/**
* this return the data publisher for the tenant.
* This returns the data publisher for the tenant based on the analytics node id.
*
* @param analyticsConfig Analytics configurations
* @param receiverURLSet Data receiver URL set as string
* @return instance of data publisher
* @throws DataPublisherConfigurationException
*
* @throws DataPublisherConfigurationException on exception
*/
public DataPublisher getDataPublisher() throws DataPublisherConfigurationException {
if (this.dataPublisher == null) {
public DataPublisher getDataPublisher(AnalyticsConfiguration analyticsConfig, String receiverURLSet)
throws DataPublisherConfigurationException {
synchronized (this) {
if (this.dataPublisher == null) {
AnalyticsConfiguration analyticsConfig = AnalyticsConfiguration.getInstance();
if (!analyticsConfig.isEnable()) {
return null;
}
if (this.dataPublishers.containsKey(receiverURLSet)) {
return this.dataPublishers.get(receiverURLSet);
} else {
String analyticsServerUrlGroups = analyticsConfig.getReceiverServerUrl();
String analyticsServerUsername = analyticsConfig.getAdminUsername();
String analyticsServerPassword = analyticsConfig.getAdminPassword();
try {
this.dataPublisher = new DataPublisher(analyticsServerUrlGroups, analyticsServerUsername,
DataPublisher dataPublisher = new DataPublisher(receiverURLSet, analyticsServerUsername,
analyticsServerPassword);
this.dataPublishers.put(receiverURLSet, dataPublisher);
return dataPublisher;
} catch (DataEndpointAgentConfigurationException e) {
throw new DataPublisherConfigurationException("Configuration Exception on data publisher for " +
"ReceiverGroup = " + analyticsServerUrlGroups + " for username " + analyticsServerUsername, e);
String msg = "Configuration Exception on data publisher for " +
"ReceiverGroup = " + analyticsServerUrlGroups + " for username " + analyticsServerUsername;
log.error(msg, e);
throw new DataPublisherConfigurationException(msg, e);
} catch (DataEndpointException e) {
throw new DataPublisherConfigurationException("Invalid ReceiverGroup = " + analyticsServerUrlGroups, e);
String msg = "Invalid ReceiverGroup = " + analyticsServerUrlGroups;
log.error(msg, e);
throw new DataPublisherConfigurationException(msg, e);
} catch (DataEndpointConfigurationException e) {
throw new DataPublisherConfigurationException("Invalid Data endpoint configuration.", e);
String msg = "Invalid Data endpoint configuration.";
log.error(msg, e);
throw new DataPublisherConfigurationException(msg, e);
} catch (DataEndpointAuthenticationException e) {
throw new DataPublisherConfigurationException("Authentication Failed for user " +
analyticsServerUsername, e);
String msg = "Authentication Failed for user " + analyticsServerUsername;
log.error(msg, e);
throw new DataPublisherConfigurationException(msg, e);
} catch (TransportException e) {
throw new DataPublisherConfigurationException("Error occurred while retrieving data publisher", e);
}
} else {
return this.dataPublisher;
String msg = "Error occurred while retrieving data publisher";
log.error(msg, e);
throw new DataPublisherConfigurationException(msg, e);
}
}
}
return this.dataPublisher;
}
}

@ -39,6 +39,7 @@ import java.io.File;
public class AnalyticsConfiguration {
private String receiverServerUrl;
private String analyticsPublisherUrl;
private String adminUsername;
private String adminPassword;
private boolean enable;
@ -81,13 +82,22 @@ public class AnalyticsConfiguration {
@XmlElement(name = "ReceiverServerUrl", required = true)
public String getReceiverServerUrl() {
return receiverServerUrl;
return DataPublisherUtil.replaceProperty(receiverServerUrl);
}
public void setReceiverServerUrl(String receiverServerUrl) {
this.receiverServerUrl = receiverServerUrl;
}
@XmlElement(name = "AnalyticsPublisherUrl", required = true)
public String getAnalyticsPublisherUrl() {
return DataPublisherUtil.replaceProperty(analyticsPublisherUrl);
}
public void setAnalyticsPublisherUrl(String analyticsPublisherUrl) {
this.analyticsPublisherUrl = analyticsPublisherUrl;
}
@XmlElement(name = "Enabled", required = true)
public boolean isEnable() {
return enable;
@ -102,8 +112,14 @@ public class AnalyticsConfiguration {
}
public static void init(String analyticsConfigPath) throws DataPublisherConfigurationException {
try {
File authConfig = new File(analyticsConfigPath);
if (!authConfig.exists()) {
log.warn(DEVICE_ANALYTICS_CONFIG_PATH + " does not exist. Disabling AnalyticsConfiguration.");
config = new AnalyticsConfiguration();
config.setEnable(false);
return;
}
try {
Document doc = DataPublisherUtil.convertToDocument(authConfig);
/* Un-marshaling device analytics configuration */

@ -21,7 +21,7 @@ package org.wso2.carbon.device.mgt.analytics.data.publisher.service;
import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublisherConfigurationException;
/**
* This service can be used to publish and retreive data from the Analytics Server.
* This service can be used to publish and retrieve data from the Analytics Server.
*/
public interface EventsPublisherService {
@ -32,8 +32,8 @@ public interface EventsPublisherService {
* @param metaDataArray - meta data that needs to pushed
* @param correlationDataArray - correlation data that needs to be pushed
* @param payloadDataArray - payload data that needs to be pushed
* @return
* @throws DataPublisherConfigurationException
* @return if success returns true
* @throws DataPublisherConfigurationException on exception
*/
boolean publishEvent(String streamName, String version, Object[] metaDataArray, Object[] correlationDataArray,
Object[] payloadDataArray) throws DataPublisherConfigurationException;

@ -24,44 +24,67 @@ import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.databridge.agent.DataPublisher;
import org.wso2.carbon.databridge.commons.utils.DataBridgeCommonsUtils;
import org.wso2.carbon.device.mgt.analytics.data.publisher.DataPublisherUtil;
import org.wso2.carbon.device.mgt.analytics.data.publisher.DeviceDataPublisher;
import org.wso2.carbon.device.mgt.analytics.data.publisher.config.AnalyticsConfiguration;
import org.wso2.carbon.device.mgt.analytics.data.publisher.exception.DataPublisherConfigurationException;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import java.util.ArrayList;
/**
* This is the implementation of Osgi Service which can be used to publish and retireved
* event/records.
*/
public class EventsPublisherServiceImpl implements EventsPublisherService {
private static Log log = LogFactory.getLog(EventsPublisherServiceImpl.class);
/**
* @param streamName is the name of the stream that the data needs to pushed
* @param version is the version of the stream
* @param metaDataArray - meta data that needs to pushed
* @param correlationDataArray - correlation data that needs to be pushed
* @param payloadDataArray - payload data that needs to be pushed
* @param metaDataArray meta data that needs to pushed
* @param correlationDataArray correlation data that needs to be pushed
* @param payloadDataArray payload data that needs to be pushed
* @return if success returns true
* @throws DataPublisherConfigurationException
* @throws DataPublisherConfigurationException on exception
*/
@Override
public boolean publishEvent(String streamName, String version, Object[] metaDataArray,
Object[] correlationDataArray,
Object[] payloadDataArray) throws DataPublisherConfigurationException {
AnalyticsConfiguration analyticsConfig = AnalyticsConfiguration.getInstance();
if (!analyticsConfig.isEnable()) {
log.warn("Analytics data publishing not enabled.");
return false;
}
if (metaDataArray == null || metaDataArray.length == 0) {
String msg = "meta data[0] must have the device Id field";
log.error(msg);
throw new DataPublisherConfigurationException(msg);
}
ArrayList<String> receiverURLGroups = DataPublisherUtil.getEndpointGroups(analyticsConfig.getReceiverServerUrl());
int hashId = DataPublisherUtil.obtainHashId(metaDataArray[0].toString(), receiverURLGroups.size());
if (receiverURLGroups.size() <= hashId) {
String msg = "Invalid receiver url group size. Expected to be higher than: " + hashId + " Actual: " +
receiverURLGroups.size();
log.error(msg);
throw new DataPublisherConfigurationException(msg);
}
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
if (!tenantDomain.equals(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME)) {
if (metaDataArray == null || metaDataArray.length == 0) {
throw new DataPublisherConfigurationException("meta data[0] should have the device Id field");
} else {
metaDataArray[0] = tenantDomain + "@" + metaDataArray[0];
}
}
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext.getThreadLocalCarbonContext()
.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true);
try {
DataPublisher dataPublisher = DeviceDataPublisher.getInstance().getDataPublisher();
DataPublisher dataPublisher = DeviceDataPublisher.getInstance()
.getDataPublisher(analyticsConfig, receiverURLGroups.get(hashId));
if (dataPublisher != null) {
String streamId = DataBridgeCommonsUtils.generateStreamId(streamName, version);
return dataPublisher.tryPublish(streamId, System.currentTimeMillis(), metaDataArray,

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. 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/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>device-mgt</artifactId>
<groupId>org.wso2.carbon.devicemgt</groupId>
<version>3.1.34-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.device.mgt.analytics.wsproxy</artifactId>
<packaging>war</packaging>
<name>WSO2 - Webapp for Web Socket Proxy</name>
<url>http://wso2.org</url>
<dependencies>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.analytics.data.publisher</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
</dependencies>
<build>
<finalName>secured-websocket-proxy</finalName>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile>
</configuration>
<executions>
<execution>
<id>jacoco-initialize</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>jacoco-site</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile>
<outputDirectory>${basedir}/target/coverage-reports/site</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,37 @@
/*
* Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.device.mgt.analytics.wsproxy.exception;
/**
* Indicates an error with deployment tinkerer
*
* @since 1.0.0
*/
public class WSProxyException extends Exception {
/**
* Constructs a new exception with the message provided and the cause.
*
* @param message the detailed message of the exception
* @param cause the cause of the exception
*/
public WSProxyException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,146 @@
/*
* Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.device.mgt.analytics.wsproxy.inbound;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.databridge.agent.exception.DataEndpointConfigurationException;
import org.wso2.carbon.device.mgt.analytics.data.publisher.DataPublisherUtil;
import org.wso2.carbon.device.mgt.analytics.data.publisher.config.AnalyticsConfiguration;
import org.wso2.carbon.device.mgt.analytics.wsproxy.exception.WSProxyException;
import org.wso2.carbon.device.mgt.analytics.wsproxy.outbound.AnalyticsClient;
import javax.websocket.CloseReason;
import javax.websocket.Session;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Interface for subscription and un-subscription for web socket
*/
public class SubscriptionEndpoint {
private static final Log log = LogFactory.getLog(SubscriptionEndpoint.class);
private Map<String, List<AnalyticsClient>> analyticsClientsMap = new HashMap<>();
/**
* Web socket onOpen - When client sends a message
*
* @param session - Users registered session.
*/
public void onOpen(Session session) {
if (log.isDebugEnabled()) {
log.debug("WebSocket opened, for Session id: " + session.getId());
}
AnalyticsConfiguration analyticsConfig = AnalyticsConfiguration.getInstance();
ArrayList<String> publisherGroups =
DataPublisherUtil.getEndpointGroups(analyticsConfig.getAnalyticsPublisherUrl());
List<AnalyticsClient> analyticsClients = new ArrayList<>();
for (String publisherURLGroup : publisherGroups) {
try {
String[] endpoints = DataPublisherUtil.getEndpoints(publisherURLGroup);
for (String endpoint : endpoints) {
try {
endpoint = endpoint.trim();
if (!endpoint.endsWith("/")) {
endpoint += "/";
}
endpoint += session.getRequestURI().getSchemeSpecificPart().replace("secured-websocket-proxy","");
AnalyticsClient analyticsClient = new AnalyticsClient(session);
analyticsClient.connectClient(new URI(endpoint));
analyticsClients.add(analyticsClient);
} catch (URISyntaxException e) {
log.error("Unable to create URL from: " + endpoint, e);
} catch (WSProxyException e) {
log.error("Unable to create WS client for: " + endpoint, e);
}
}
} catch (DataEndpointConfigurationException e) {
log.error("Unable to obtain endpoints from receiverURLGroup: " + publisherURLGroup, e);
}
}
if (log.isDebugEnabled()) {
log.debug("Configured " + analyticsClients.size() + " analytics clients for Session id: " +
session.getId());
}
analyticsClientsMap.put(session.getId(), analyticsClients);
}
/**
* Web socket onClose - Remove the registered sessions
*
* @param session - Users registered session.
* @param reason - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
* @param tenantDomain - Domain of the tenant.
*/
public void onClose(Session session, CloseReason reason, String streamName, String version, String tenantDomain) {
if (log.isDebugEnabled()) {
log.debug("Closing a WebSocket due to " + reason.getReasonPhrase() + ", for session ID:" +
session.getId() + ", for request URI - " + session.getRequestURI());
}
for (AnalyticsClient analyticsClient : analyticsClientsMap.get(session.getId())) {
if (analyticsClient != null) {
try {
analyticsClient.closeConnection(reason);
} catch (WSProxyException e) {
log.error("Error occurred while closing ws connection due to " + reason.getReasonPhrase() +
", for session ID:" + session.getId() + ", for request URI - " + session.getRequestURI(), e);
}
}
}
analyticsClientsMap.remove(session.getId());
}
/**
* Web socket onMessage - When client sens a message
*
* @param session - Users registered session.
* @param message - Status code for web-socket close.
*/
public void onMessage(Session session, String message) {
for (AnalyticsClient analyticsClient : analyticsClientsMap.get(session.getId())) {
if (analyticsClient != null) {
analyticsClient.sendMessage(message);
}
}
}
/**
* Web socket onError
*
* @param session - Users registered session.
* @param throwable - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
* @param tenantDomain - Domain of the tenant.
*/
public void onError(Session session, Throwable throwable, String streamName, String version, String tenantDomain) {
log.error("Error occurred in session ID: " + session.getId() + ", for request URI - " +
session.getRequestURI() + ", " + throwable.getMessage(), throwable);
}
}

@ -0,0 +1,104 @@
/*
* Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.device.mgt.analytics.wsproxy.inbound;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.base.MultitenantConstants;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
/**
* Connect to web socket with Super tenant
*/
@ServerEndpoint(value = "/{destination}/{streamname}/{version}")
public class SuperTenantSubscriptionEndpoint extends SubscriptionEndpoint {
private static final Log log = LogFactory.getLog(SuperTenantSubscriptionEndpoint.class);
/**
* Web socket onOpen - When client sends a message
*
* @param session - Users registered session.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config, @PathParam("streamname") String streamName,
@PathParam("version") String version) {
if (log.isDebugEnabled()) {
log.debug("WebSocket opened, for Session id: " + session.getId() + ", for the Stream:" + streamName);
}
super.onOpen(session);
}
/**
* Web socket onMessage - When client sens a message
*
* @param session - Users registered session.
* @param message - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
*/
@OnMessage
public void onMessage(Session session, String message, @PathParam("streamname") String streamName) {
if (log.isDebugEnabled()) {
log.debug("Received message from client. Message: " + message + ", " +
"for Session id: " + session.getId() + ", for the Stream:" + streamName);
}
super.onMessage(session, message);
}
/**
* Web socket onClose - Remove the registered sessions
*
* @param session - Users registered session.
* @param reason - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
*/
@OnClose
public void onClose(Session session, CloseReason reason, @PathParam("streamname") String streamName,
@PathParam("version") String version) {
super.onClose(session, reason, streamName, version, MultitenantConstants.SUPER_TENANT_NAME);
}
/**
* Web socket onError - Remove the registered sessions
*
* @param session - Users registered session.
* @param throwable - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
*/
@OnError
public void onError(Session session, Throwable throwable, @PathParam("streamname") String streamName,
@PathParam("version") String version) {
super.onError(session, throwable, streamName, version, MultitenantConstants.SUPER_TENANT_NAME);
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.device.mgt.analytics.wsproxy.inbound;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
/**
* Connect to web socket with a tenant
*/
@ServerEndpoint(value = "/{destination}/t/{tdomain}/{streamname}/{version}")
public class TenantSubscriptionEndpoint extends SubscriptionEndpoint {
private static final Log log = LogFactory.getLog(TenantSubscriptionEndpoint.class);
/**
* Web socket onOpen - When client sends a message
*
* @param session - Users registered session.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
* @param tdomain - Tenant domain extracted from ws url.
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config, @PathParam("streamname") String streamName,
@PathParam("version") String version, @PathParam("tdomain") String tdomain) {
if (log.isDebugEnabled()) {
log.debug("WebSocket opened, for Session id: " + session.getId() + ", for the Stream:" + streamName);
}
super.onOpen(session);
}
/**
* Web socket onMessage - When client sens a message
*
* @param session - Users registered session.
* @param message - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
*/
@OnMessage
public void onMessage(Session session, String message, @PathParam("streamname") String streamName, @PathParam("tdomain") String tdomain) {
if (log.isDebugEnabled()) {
log.debug("Received message from client. Message: " + message + ", for Session id: " +
session.getId() + ", for tenant domain" + tdomain + ", for the Adaptor:" + streamName);
}
super.onMessage(session, message);
}
/**
* Web socket onClose - Remove the registered sessions
*
* @param session - Users registered session.
* @param reason - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
*/
@OnClose
public void onClose(Session session, CloseReason reason, @PathParam("streamname") String streamName,
@PathParam("version") String version, @PathParam("tdomain") String tdomain) {
super.onClose(session, reason, streamName, version, tdomain);
}
/**
* Web socket onError - Remove the registered sessions
*
* @param session - Users registered session.
* @param throwable - Status code for web-socket close.
* @param streamName - StreamName extracted from the ws url.
* @param version - Version extracted from the ws url.
*/
@OnError
public void onError(Session session, Throwable throwable, @PathParam("streamname") String streamName,
@PathParam("version") String version, @PathParam("tdomain") String tdomain) {
super.onError(session, throwable, streamName, version, tdomain);
}
}

@ -0,0 +1,120 @@
/*
* Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.wso2.carbon.device.mgt.analytics.wsproxy.outbound;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.device.mgt.analytics.wsproxy.exception.WSProxyException;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.net.URI;
/**
* This class holds web socket client implementation
*
* @since 1.0.0
*/
@javax.websocket.ClientEndpoint
public class AnalyticsClient {
private static final Log log = LogFactory.getLog(AnalyticsClient.class);
private WebSocketContainer container;
private Session analyticsSession = null;
private Session clientSession;
/**
* Create {@link AnalyticsClient} instance.
*/
public AnalyticsClient(Session clientSession) {
container = ContainerProvider.getWebSocketContainer();
this.clientSession = clientSession;
}
/**
* Create web socket client connection using {@link WebSocketContainer}.
*/
public void connectClient(URI endpointURI) throws WSProxyException {
try {
analyticsSession = container.connectToServer(this, endpointURI);
} catch (DeploymentException | IOException e) {
String msg = "Error occurred while connecting to remote endpoint " + endpointURI.toString();
log.error(msg, e);
throw new WSProxyException(msg, e);
}
}
/**
* Callback hook for Connection close events.
*
* @param userSession the analyticsSession which is getting closed.
* @param reason the reason for connection close
*/
@OnClose
public void onClose(Session userSession, CloseReason reason) {
if (log.isDebugEnabled()) {
log.debug("Closing web socket session: '" + userSession.getId() + "'. Code: " +
reason.getCloseCode().toString() + " Reason: " + reason.getReasonPhrase());
}
this.analyticsSession = null;
}
/**
* Callback hook for Message Events.
*
* <p>This method will be invoked when a client send a message.
*
* @param message The text message.
*/
@OnMessage
public void onMessage(String message) {
this.clientSession.getAsyncRemote().sendText(message);
}
/**
* Send a message.
*
* @param message the message which is going to send.
*/
public void sendMessage(String message) {
this.analyticsSession.getAsyncRemote().sendText(message);
}
/**
* Close current connection.
*/
public void closeConnection(CloseReason closeReason) throws WSProxyException {
if (this.analyticsSession != null) {
try {
this.analyticsSession.close(closeReason);
} catch (IOException e) {
String msg = "Error on closing WS connection.";
log.error(msg, e);
throw new WSProxyException(msg, e);
}
}
}
}

@ -0,0 +1,46 @@
<!--
~ Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. 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.
-->
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Output WebSocket Proxy</display-name>
<filter>
<filter-name>ContentTypeBasedCachePreventionFilter</filter-name>
<filter-class>org.wso2.carbon.ui.filters.cache.ContentTypeBasedCachePreventionFilter</filter-class>
<init-param>
<param-name>patterns</param-name>
<param-value>text/html" ,application/json" ,text/plain</param-value>
</init-param>
<init-param>
<param-name>filterAction</param-name>
<param-value>enforce</param-value>
</init-param>
<init-param>
<param-name>httpHeaders</param-name>
<param-value>Cache-Control: no-store, no-cache, must-revalidate, private</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ContentTypeBasedCachePreventionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

@ -5,7 +5,7 @@
"managerHTTPSURL": "https://%iot.manager.host%:%iot.manager.https.port%",
"httpsURL": "https://%iot.gateway.host%:%iot.gateway.https.port%",
"httpURL": "http://%iot.gateway.host%:%iot.gateway.http.port%",
"wssURL": "https://%iot.analytics.host%:%iot.analytics.https.port%",
"wssURL": "https://%iot.core.host%:%iot.core.https.port%",
"remoteSessionWSURL": "https://%iot.core.host%:%iot.core.https.port%",
"portalURL": "https://%iot.analytics.host%:%iot.analytics.https.port%",
"dashboardServerURL": "%https.ip%",

@ -43,14 +43,14 @@ function onRequest(context) {
if (tokenPair) {
token = tokenPair.accessToken;
}
websocketEndpoint = websocketEndpoint + "/secured-websocket/iot.per.device.stream." + tenantDomain + "." + device.type + "/1.0.0?"
websocketEndpoint = websocketEndpoint + "/secured-websocket-proxy/secured-websocket/iot.per.device.stream." + tenantDomain + "." + device.type + "/1.0.0?"
+ "deviceId=" + device.deviceIdentifier + "&deviceType=" + device.type + "&websocketToken=" + token;
} else {
var tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username + "@" + tenantDomain,"default", {});
if (tokenPair) {
token = tokenPair.accessToken;
}
websocketEndpoint = websocketEndpoint + "/secured-websocket" + "/t/" + tenantDomain + "/iot.per.device.stream." + tenantDomain
websocketEndpoint = websocketEndpoint + "/secured-websocket-proxy/secured-websocket/t/" + tenantDomain + "/iot.per.device.stream." + tenantDomain
+ "." + device.type + "/1.0.0?" + "deviceId=" + device.deviceIdentifier + "&deviceType="
+ device.type + "&websocketToken=" + token;
}

@ -37,14 +37,14 @@ function onRequest(context) {
tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username,"default", {});
if (tokenPair) {
token = tokenPair.accessToken;
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket/";
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket-proxy/secured-websocket/";
}
} else {
tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username + "@" +
context.user.domain, "default", {});
if (tokenPair) {
token = tokenPair.accessToken;
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket/t/" +
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket-proxy/secured-websocket/t/" +
context.user.domain + "/";
}
}

@ -32,7 +32,7 @@ var ApplicationOptions = {
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'iot.per.device.stream.geo.FusedSpatialEvent',
CEP_ON_ALERT_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'org.wso2.geo.AlertsNotifications',
CEP_Traffic_STREAM_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'DefaultWebsocketOutputAdaptorOnTrafficStream',
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_WEBAPP_NAME: 'secured-websocket',
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_WEBAPP_NAME: 'secured-websocket-proxy',
TENANT_INDEX: 't',
COLON : ':',
PATH_SEPARATOR : '/',

@ -38,13 +38,13 @@ function onRequest(context) {
tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username,"default", {});
if (tokenPair) {
token = tokenPair.accessToken;
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket/";
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket-proxy/secured-websocket/";
}
} else {
tokenPair = jwtClient.getAccessToken(resp[0], resp[1], context.user.username + "@" + context.user.domain,"default", {});
if (tokenPair) {
token = tokenPair.accessToken;
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket/t/"+context.user.domain+"/";
wsEndpoint = devicemgtProps["wssURL"].replace("https", "wss") + "/secured-websocket-proxy/secured-websocket/t/"+context.user.domain+"/";
}
}

@ -32,7 +32,7 @@ var ApplicationOptions = {
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'iot.per.device.stream.geo.FusedSpatialEvent',
CEP_ON_ALERT_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'org.wso2.geo.AlertsNotifications',
CEP_Traffic_STREAM_WEB_SOCKET_OUTPUT_ADAPTOR_NAME: 'DefaultWebsocketOutputAdaptorOnTrafficStream',
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_WEBAPP_NAME: 'secured-websocket',
CEP_WEB_SOCKET_OUTPUT_ADAPTOR_WEBAPP_NAME: 'secured-websocket-proxy',
TENANT_INDEX: 't',
COLON : ':',
PATH_SEPARATOR : '/',

@ -41,6 +41,7 @@
<module>org.wso2.carbon.device.mgt.v09.api</module>
<module>org.wso2.carbon.device.mgt.analytics.data.publisher</module>
<module>org.wso2.carbon.device.mgt.url.printer</module>
<module>org.wso2.carbon.device.mgt.analytics.wsproxy</module>
</modules>
</project>

@ -1,2 +0,0 @@
instructions.configure = \
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.analytics.data.publisher_${feature.version}/conf/device-analytics-config.xml,target:${installFolder}/../../conf/etc/device-analytics-config.xml,overwrite:true);\

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~ Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
@ -27,18 +27,22 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.device.mgt.analytics.data.publisher.feature</artifactId>
<artifactId>org.wso2.carbon.device.mgt.analytics.feature</artifactId>
<packaging>pom</packaging>
<version>3.1.34-SNAPSHOT</version>
<name>WSO2 Carbon - Device Management Server Feature</name>
<url>http://wso2.org</url>
<description>This feature contains bundles related to device analytics data publisher</description>
<description>This feature contains bundles related to device analytics data publisher and ws proxy</description>
<dependencies>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.analytics.data.publisher</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.analytics.wsproxy</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.registry</groupId>
<artifactId>org.wso2.carbon.registry.indexing</artifactId>
@ -51,6 +55,34 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.analytics.wsproxy</artifactId>
<version>${project.version}</version>
<type>war</type>
<overWrite>true</overWrite>
<outputDirectory>
${project.build.directory}/maven-shared-archive-resources/webapps
</outputDirectory>
<destFileName>secured-websocket-proxy.war</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
@ -88,7 +120,7 @@
<goal>p2-feature-gen</goal>
</goals>
<configuration>
<id>org.wso2.carbon.device.mgt.analytics.data.publisher</id>
<id>org.wso2.carbon.device.mgt.analytics</id>
<propertiesFile>../../../features/etc/feature.properties</propertiesFile>
<adviceFile>
<properties>

@ -18,6 +18,7 @@
-->
<AnalyticsConfiguration>
<Enabled>true</Enabled>
<!--
Server URL of the remote DAS/BAM/CEP server used to collect statistics. Must
be specified in protocol://hostname:port/ format.
@ -29,8 +30,20 @@
Ex - Multiple Receiver Groups with two receivers each
{tcp://localhost:7612/,tcp://localhost:7613},{tcp://localhost:7712/,tcp://localhost:7713/}
-->
<Enabled>false</Enabled>
<ReceiverServerUrl>tcp://localhost:7612</ReceiverServerUrl>
<ReceiverServerUrl>tcp://${iot.analytics.host}:${iot.analytics.thrift.port}</ReceiverServerUrl>
<!--
Server URL of the remote DAS/BAM/CEP server used to subscribe for statistics via secured web sockets.
Must be specified in wss://hostname:port/ format. Analytics Publishers should defined per each receiver
server url.
Multiple AnalyticsPublisherUrl properties can be defined as Groups each having one or more publishers.
Publisher groups are delimited by curly braces whereas publishers are delimited by commas.
Ex - Multiple publishers within a single group
wss://localhost:9445/,wss://localhost:9446/,wss://localhost:9447/
Ex - Multiple Publisher Groups with two publishers each
{wss://localhost:9445/,wss://localhost:9446/},{wss://localhost:9447/,wss://localhost:9448/}
-->
<AnalyticsPublisherUrl>wss://${iot.analytics.host}:${iot.analytics.https.port}</AnalyticsPublisherUrl>
<AdminUsername>admin</AdminUsername>
<AdminPassword>admin</AdminPassword>
</AnalyticsConfiguration>

@ -0,0 +1,4 @@
instructions.configure = \
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.analytics_${feature.version}/conf/device-analytics-config.xml,target:${installFolder}/../../conf/etc/device-analytics-config.xml,overwrite:true);\
org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\
org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.wso2.carbon.device.mgt.analytics_${feature.version}/webapps/secured-websocket-proxy.war,target:${installFolder}/../../deployment/server/webapps/secured-websocket-proxy.war,overwrite:true);\

@ -39,7 +39,7 @@
<module>org.wso2.carbon.device.mgt.api.feature</module>
<module>org.wso2.carbon.device.mgt.feature</module>
<module>org.wso2.carbon.device.mgt.extensions.feature</module>
<module>org.wso2.carbon.device.mgt.analytics.data.publisher.feature</module>
<module>org.wso2.carbon.device.mgt.analytics.feature</module>
</modules>
</project>

@ -241,6 +241,11 @@
<artifactId>org.wso2.carbon.device.mgt.analytics.data.publisher</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.analytics.wsproxy</artifactId>
<version>${carbon.device.mgt.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>org.wso2.carbon.device.mgt.server.feature</artifactId>
@ -1307,11 +1312,11 @@
<scope>provided</scope>
</dependency>
<!--Tomcat-->
<!--Mysql Connector version-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${tomcat.version}</version>
<version>${mysql.connector.version}</version>
<scope>test</scope>
</dependency>
@ -1647,6 +1652,18 @@
<scope>test</scope>
<version>${slf4j.nop.version}</version>
</dependency>
<!--websocket dependency-->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>${javax.websocket.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-websocket-api</artifactId>
<version>${tomcat.websocket.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
@ -1909,8 +1926,8 @@
<orbit.version.h2.engine>1.2.140.wso2v3</orbit.version.h2.engine>
<!-- Tomcat -->
<orbit.version.tomcat>7.0.59.wso2v1</orbit.version.tomcat>
<orbit.version.tomcat.servlet.api>7.0.59.wso2v1</orbit.version.tomcat.servlet.api>
<orbit.version.tomcat>7.0.85.wso2v1</orbit.version.tomcat>
<orbit.version.tomcat.servlet.api>7.0.85.wso2v1</orbit.version.tomcat.servlet.api>
<tomcat.jdbc.pooling.version>7.0.34.wso2v2</tomcat.jdbc.pooling.version>
<!-- Carbon Deployment -->
@ -2018,8 +2035,8 @@
<commons-collections.version.range>(3.2.0, 3.3.0]</commons-collections.version.range>
<commons-configuration.version>1.8</commons-configuration.version>
<!--Tomcat version-->
<tomcat.version>5.1.34</tomcat.version>
<!--Mysql connector version-->
<mysql.connector.version>5.1.34</mysql.connector.version>
<!-- XMPP/MQTT Version -->
<smack.wso2.version>3.0.4.wso2v1</smack.wso2.version>
@ -2087,6 +2104,10 @@
<commons.dbcp.version>1.4.0.wso2v1</commons.dbcp.version>
<slf4j.nop.version>1.7.25</slf4j.nop.version>
<!--websocket related lib versions-->
<tomcat.websocket.version>7.0.85</tomcat.websocket.version>
<javax.websocket.version>1.0</javax.websocket.version>
<glassfish.tyrus.version>1.13.1</glassfish.tyrus.version>
</properties>
</project>

Loading…
Cancel
Save