Skip to content

Commit 2dff173

Browse files
authored
Merge pull request #22 from arrowhead-f/development
WebSocket support
2 parents 8d6c610 + e8f6a6d commit 2dff173

3 files changed

Lines changed: 154 additions & 3 deletions

File tree

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<groupId>eu.arrowhead</groupId>
1212
<artifactId>client-library</artifactId>
13-
<version>4.1.3.13</version>
13+
<version>4.3.0.0</version>
1414
<packaging>jar</packaging>
1515
<name>client-library-java-spring</name>
1616
<description>Arrowhead Client Library using Spring Boot</description>
@@ -82,6 +82,11 @@
8282
<scope>test</scope>
8383
</dependency>
8484

85+
<dependency>
86+
<groupId>org.springframework</groupId>
87+
<artifactId>spring-websocket</artifactId>
88+
</dependency>
89+
8590
<dependency>
8691
<groupId>org.bitbucket.b_c</groupId>
8792
<artifactId>jose4j</artifactId>

src/main/java/eu/arrowhead/client/library/ArrowheadService.java

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import java.util.Base64;
99
import java.util.List;
1010
import java.util.Map;
11-
11+
//import java.io.FileInputStream;
12+
import java.io.IOException;
1213
import javax.annotation.Resource;
1314

1415
import org.apache.logging.log4j.LogManager;
@@ -22,11 +23,26 @@
2223
import org.springframework.util.MultiValueMap;
2324
import org.springframework.web.util.UriComponents;
2425
import org.springframework.web.util.UriComponentsBuilder;
26+
import org.springframework.web.socket.client.WebSocketConnectionManager;
27+
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
28+
import org.springframework.web.socket.WebSocketHandler;
29+
30+
import org.apache.http.ssl.TrustStrategy;
31+
import javax.net.ssl.SSLContext;
32+
import org.apache.http.ssl.SSLContexts;
33+
import java.security.cert.X509Certificate;
34+
import java.security.KeyStore;
35+
import java.security.KeyStoreException;
36+
import java.security.NoSuchAlgorithmException;
37+
import java.security.cert.CertificateException;
38+
import java.util.ServiceConfigurationError;
39+
import java.util.UUID;
2540

2641
import eu.arrowhead.client.library.util.ClientCommonConstants;
2742
import eu.arrowhead.client.library.util.CoreServiceUri;
2843
import eu.arrowhead.common.CommonConstants;
2944
import eu.arrowhead.common.SSLProperties;
45+
//import eu.arrowhead.common.SecurityUtilities;
3046
import eu.arrowhead.common.Utilities;
3147
import eu.arrowhead.common.core.CoreSystem;
3248
import eu.arrowhead.common.core.CoreSystemService;
@@ -70,7 +86,7 @@ public class ArrowheadService {
7086

7187
@Resource(name = CommonConstants.ARROWHEAD_CONTEXT)
7288
private Map<String,Object> arrowheadContext;
73-
89+
7490
@Autowired
7591
private SSLProperties sslProperties;
7692

@@ -453,6 +469,105 @@ public <T> T consumeServiceHTTP(final Class<T> responseType, final HttpMethod ht
453469
return response.getBody();
454470
}
455471

472+
//-------------------------------------------------------------------------------------------------
473+
/**
474+
* Make WS(S) connection with the specified service reachability details.
475+
*
476+
* @param handler WebSocket handler to use for message exchanges
477+
* @param address String value which represents the host where the service is available.
478+
* @param port int value which represents the port where the service is available
479+
* @param serviceUri String value which represents the URI where the service is available.
480+
* @param token (nullable) String value which represents the token for being authorized at the provider side if necessary. Token could be received in orchestration response per interface type.
481+
* @param queryParams (nullable) String... variable arguments which represent the additional key-value http(s) query parameters if any necessary. E.g.: "k1", "v1", "k2", "v2".
482+
* @return A string ID for the WebSocketConnection manager created and used for the full-duplex communication
483+
*
484+
* @throws InvalidParameterException when service URL can't be assembled.
485+
* @throws ArrowheadException when WSS connection failed or the communication is managed via Gateway Core System and internal server error happened.
486+
*/
487+
public String connnectServiceWS(final WebSocketHandler handler, final String address, final int port, final String serviceUri, final String token, final String... queryParams) {
488+
if (handler == null) {
489+
throw new InvalidParameterException("handler cannot be null.");
490+
}
491+
if (Utilities.isEmpty(address)) {
492+
throw new InvalidParameterException("address cannot be null or blank.");
493+
}
494+
if (Utilities.isEmpty(serviceUri)) {
495+
throw new InvalidParameterException("serviceUri cannot be null or blank.");
496+
}
497+
if (port < CommonConstants.SYSTEM_PORT_RANGE_MIN || port > CommonConstants.SYSTEM_PORT_RANGE_MAX){
498+
throw new InvalidParameterException("Port must be between " + CommonConstants.SYSTEM_PORT_RANGE_MIN + " and " + CommonConstants.SYSTEM_PORT_RANGE_MAX + ".");
499+
}
500+
501+
/* Handle query parameters */
502+
String[] validatedQueryParams;
503+
if (queryParams == null) {
504+
validatedQueryParams = new String[0];
505+
} else {
506+
validatedQueryParams = queryParams;
507+
}
508+
509+
/* prepare the URI */
510+
UriComponents uri;
511+
if(!Utilities.isEmpty(token)) {
512+
final List<String> query = new ArrayList<>();
513+
query.addAll(Arrays.asList(validatedQueryParams));
514+
query.add(CommonConstants.REQUEST_PARAM_TOKEN);
515+
query.add(token);
516+
uri = Utilities.createURI(getUriSchemeWS(), address, port, serviceUri, query.toArray(new String[query.size()]));
517+
} else {
518+
uri = Utilities.createURI(getUriSchemeWS(), address, port, serviceUri, validatedQueryParams);
519+
}
520+
521+
/* try to establish WS(S) connection */
522+
final StandardWebSocketClient wsClient = new StandardWebSocketClient();
523+
if(sslProperties.isSslEnabled()) {
524+
try {
525+
final KeyStore trustStore = getTrustStore();
526+
final KeyStore keyStore = getKeyStore();
527+
final TrustStrategy acceptingTrustStrategy = (final X509Certificate[] chain, final String authType) -> true;
528+
final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, acceptingTrustStrategy).loadKeyMaterial(keyStore, sslProperties.getKeyStorePassword().toCharArray()).build();
529+
530+
wsClient.getUserProperties().clear();
531+
wsClient.getUserProperties().put(ClientCommonConstants.TOMCAT_WS_SSL_CONTEXT, sslContext);
532+
} catch(final Exception e) {
533+
throw new ArrowheadException("WSS connection failed: " + e.getMessage());
534+
}
535+
}
536+
537+
final WebSocketConnectionManager manager = new WebSocketConnectionManager(wsClient, handler, uri.toString());
538+
manager.start();
539+
final String managerId = ClientCommonConstants.WS_MANAGER_ID_PREFIX + UUID.randomUUID().toString();
540+
arrowheadContext.put(managerId, manager);
541+
return managerId;
542+
}
543+
544+
//-------------------------------------------------------------------------------------------------
545+
/**
546+
* Close the WS(S) connection by the given WebSocketConnectionManager ID if any.
547+
*
548+
* @param wsManagerId string id of the WebSocketConnectionManager
549+
* @return true if and only if the connection was alive, otherwise false
550+
*
551+
* @throws InvalidParameterException when wsManagerId is null or blank..
552+
*/
553+
public boolean closeWSConnection(final String wsManagerId) {
554+
if (Utilities.isEmpty(wsManagerId)) {
555+
throw new InvalidParameterException("wsManagerId cannot be null or blank.");
556+
}
557+
558+
if (arrowheadContext.containsKey(wsManagerId)) {
559+
final WebSocketConnectionManager manager = (WebSocketConnectionManager) arrowheadContext.get(wsManagerId);
560+
final boolean alive = manager.isRunning();
561+
if (alive) {
562+
manager.stop();
563+
}
564+
arrowheadContext.remove(wsManagerId);
565+
return alive;
566+
}
567+
568+
return false;
569+
}
570+
456571
//-------------------------------------------------------------------------------------------------
457572
/**
458573
* Sends a http(s) 'subscription' request to Event Handler Core System.
@@ -552,6 +667,11 @@ private ResponseEntity<ServiceQueryResultDTO> queryServiceReqistryByCoreService(
552667
private String getUriScheme() {
553668
return sslProperties.isSslEnabled() ? CommonConstants.HTTPS : CommonConstants.HTTP;
554669
}
670+
671+
//-------------------------------------------------------------------------------------------------
672+
private String getUriSchemeWS() {
673+
return sslProperties.isSslEnabled() ? "wss" : "ws"; //TODO change string literals to CommonConstants.WSS/WS when core-client-skeleton lib is updated
674+
}
555675

556676
//-------------------------------------------------------------------------------------------------
557677
private String getUriSchemeFromInterfaceName(final String interfaceName) {
@@ -578,4 +698,27 @@ private List<CoreSystemService> getPublicServicesOfCoreSystem(final CoreSystem c
578698
publicServices.retainAll(CommonConstants.PUBLIC_CORE_SYSTEM_SERVICES);
579699
return publicServices;
580700
}
701+
702+
//-------------------------------------------------------------------------------------------------
703+
private KeyStore getKeyStore() {
704+
try {
705+
final KeyStore keystore = KeyStore.getInstance(sslProperties.getKeyStoreType());
706+
keystore.load(sslProperties.getKeyStore().getInputStream(), sslProperties.getKeyStorePassword().toCharArray());
707+
return keystore;
708+
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
709+
throw new ServiceConfigurationError("Cannot open keystore: " + e.getMessage());
710+
}
711+
}
712+
713+
//-------------------------------------------------------------------------------------------------
714+
private KeyStore getTrustStore() {
715+
try {
716+
final KeyStore truststore = KeyStore.getInstance(sslProperties.getKeyStoreType());
717+
truststore.load(sslProperties.getTrustStore().getInputStream(), sslProperties.getTrustStorePassword().toCharArray());
718+
return truststore;
719+
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
720+
throw new ServiceConfigurationError("Cannot open truststore: " + e.getMessage());
721+
}
722+
}
723+
581724
}

src/main/java/eu/arrowhead/client/library/util/ClientCommonConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ public class ClientCommonConstants {
1414
public static final String TOKEN_SECURITY_FILTER_ENABLED = "token.security.filter.enabled";
1515
public static final String $TOKEN_SECURITY_FILTER_ENABLED_WD = "${" + TOKEN_SECURITY_FILTER_ENABLED + ":true" + "}";
1616
public static final String CORE_SERVICE_DEFINITION_SUFFIX = "-ah.core";
17+
18+
public static final String TOMCAT_WS_SSL_CONTEXT = "org.apache.tomcat.websocket.SSL_CONTEXT";
19+
public static final String WS_MANAGER_ID_PREFIX = "ws.manager.";
1720

1821
//=================================================================================================
1922
// assistant methods

0 commit comments

Comments
 (0)