88import java .util .Base64 ;
99import java .util .List ;
1010import java .util .Map ;
11-
11+ //import java.io.FileInputStream;
12+ import java .io .IOException ;
1213import javax .annotation .Resource ;
1314
1415import org .apache .logging .log4j .LogManager ;
2223import org .springframework .util .MultiValueMap ;
2324import org .springframework .web .util .UriComponents ;
2425import 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
2641import eu .arrowhead .client .library .util .ClientCommonConstants ;
2742import eu .arrowhead .client .library .util .CoreServiceUri ;
2843import eu .arrowhead .common .CommonConstants ;
2944import eu .arrowhead .common .SSLProperties ;
45+ //import eu.arrowhead.common.SecurityUtilities;
3046import eu .arrowhead .common .Utilities ;
3147import eu .arrowhead .common .core .CoreSystem ;
3248import 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}
0 commit comments