+ The Eclipse Foundation makes available all content in this plug-in
+ ("Content"). Unless otherwise indicated below, the Content
+ is provided to you under the terms and conditions of the Eclipse
+ Public License Version 2.0 ("EPL"). A copy of the EPL is
+ available at http://www.eclipse.org/legal/epl-2.0.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
+
+ If you did not receive this Content directly from the Eclipse
+ Foundation, the Content is being redistributed by another party
+ ("Redistributor") and different terms and conditions may
+ apply to your use of any object code in the Content. Check the
+ Redistributor's license that was provided with the Content. If no such
+ license exists, contact the Redistributor. Unless otherwise indicated
+ below, the terms and conditions of the EPL still apply to any source
+ code in the Content and such source code may be obtained at http://www.eclipse.org.
+
+
+
+
\ No newline at end of file
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/about_files/epl-v20.html b/kura/org.eclipse.kura.rest.cloudconnection.provider/about_files/epl-v20.html
new file mode 100644
index 00000000000..cc699dea65b
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/about_files/epl-v20.html
@@ -0,0 +1,301 @@
+
+
+
+
+
+ Eclipse Public License - Version 2.0
+
+
+
+
Eclipse Public License - v 2.0
+
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION
+ OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+
1. DEFINITIONS
+
“Contribution” means:
+
+
a) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+
+
+ b) in the case of each subsequent Contributor:
+
+
i) changes to the Program, and
+
ii) additions to the Program;
+
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ “originates” from a Contributor if it was added to the Program by such
+ Contributor itself or anyone acting on such Contributor's behalf.
+ Contributions do not include changes or additions to the Program that
+ are not Modified Works.
+
+
+
“Contributor” means any person or entity that Distributes the Program.
+
“Licensed Patents” mean patent claims licensable by a Contributor which
+ are necessarily infringed by the use or sale of its Contribution alone
+ or when combined with the Program.
+
+
“Program” means the Contributions Distributed in accordance with this
+ Agreement.
+
+
“Recipient” means anyone who receives the Program under this Agreement
+ or any Secondary License (as applicable), including Contributors.
+
+
“Derivative Works” shall mean any work, whether in Source Code or other
+ form, that is based on (or derived from) the Program and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship.
+
+
“Modified Works” shall mean any work in Source Code or other form that
+ results from an addition to, deletion from, or modification of the
+ contents of the Program, including, for purposes of clarity any new file
+ in Source Code form that contains any contents of the Program. Modified
+ Works shall not include works that contain only declarations, interfaces,
+ types, classes, structures, or files of the Program solely in each case
+ in order to link to, bind by name, or subclass the Program or Modified
+ Works thereof.
+
+
“Distribute” means the acts of a) distributing or b) making available
+ in any manner that enables the transfer of a copy.
+
+
“Source Code” means the form of a Program preferred for making
+ modifications, including but not limited to software source code,
+ documentation source, and configuration files.
+
+
“Secondary License” means either the GNU General Public License,
+ Version 2.0, or any later versions of that license, including any
+ exceptions or additional permissions as identified by the initial
+ Contributor.
+
+
2. GRANT OF RIGHTS
+
+
a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+
+
b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in Source Code or other form. This patent license shall
+ apply to the combination of the Contribution and the Program if,
+ at the time the Contribution is added by the Contributor, such
+ addition of the Contribution causes such combination to be covered
+ by the Licensed Patents. The patent license shall not apply to any
+ other combinations which include the Contribution. No hardware per
+ se is licensed hereunder.
+
+
c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the rights
+ and licenses granted hereunder, each Recipient hereby assumes sole
+ responsibility to secure any other intellectual property rights needed,
+ if any. For example, if a third party patent license is required to
+ allow Recipient to Distribute the Program, it is Recipient's
+ responsibility to acquire that license before distributing the Program.
+
+
d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+
e) Notwithstanding the terms of any Secondary License, no Contributor
+ makes additional grants to any Recipient (other than those set forth
+ in this Agreement) as a result of such Recipient's receipt of the
+ Program under the terms of a Secondary License (if permitted under
+ the terms of Section 3).
+
+
+
3. REQUIREMENTS
+
3.1 If a Contributor Distributes the Program in any form, then:
+
+
a) the Program must also be made available as Source Code, in
+ accordance with section 3.2, and the Contributor must accompany
+ the Program with a statement that the Source Code for the Program
+ is available under this Agreement, and informs Recipients how to
+ obtain it in a reasonable manner on or through a medium customarily
+ used for software exchange; and
+
+
+ b) the Contributor may Distribute the Program under a license
+ different than this Agreement, provided that such license:
+
+
i) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including warranties
+ or conditions of title and non-infringement, and implied warranties
+ or conditions of merchantability and fitness for a particular purpose;
+
+
ii) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special, incidental
+ and consequential damages, such as lost profits;
+
+
iii) does not attempt to limit or alter the recipients' rights in the
+ Source Code under section 3.2; and
+
+
iv) requires any subsequent distribution of the Program by any party
+ to be under a license that satisfies the requirements of this section 3.
+
+
+
+
+
3.2 When the Program is Distributed as Source Code:
+
+
a) it must be made available under this Agreement, or if the Program (i)
+ is combined with other material in a separate file or files made available
+ under a Secondary License, and (ii) the initial Contributor attached to
+ the Source Code the notice described in Exhibit A of this Agreement,
+ then the Program may be made available under the terms of such
+ Secondary Licenses, and
+
+
b) a copy of this Agreement must be included with each copy of the Program.
+
+
3.3 Contributors may not remove or alter any copyright, patent, trademark,
+ attribution notices, disclaimers of warranty, or limitations of liability
+ (‘notices’) contained within the Program from any copy of the Program which
+ they Distribute, provided that Contributors may add their own appropriate
+ notices.
+
+
4. COMMERCIAL DISTRIBUTION
+
Commercial distributors of software may accept certain responsibilities
+ with respect to end users, business partners and the like. While this
+ license is intended to facilitate the commercial use of the Program, the
+ Contributor who includes the Program in a commercial product offering should
+ do so in a manner which does not create potential liability for other
+ Contributors. Therefore, if a Contributor includes the Program in a
+ commercial product offering, such Contributor (“Commercial Contributor”)
+ hereby agrees to defend and indemnify every other Contributor
+ (“Indemnified Contributor”) against any losses, damages and costs
+ (collectively “Losses”) arising from claims, lawsuits and other legal actions
+ brought by a third party against the Indemnified Contributor to the extent
+ caused by the acts or omissions of such Commercial Contributor in connection
+ with its distribution of the Program in a commercial product offering.
+ The obligations in this section do not apply to any claims or Losses relating
+ to any actual or alleged intellectual property infringement. In order to
+ qualify, an Indemnified Contributor must: a) promptly notify the
+ Commercial Contributor in writing of such claim, and b) allow the Commercial
+ Contributor to control, and cooperate with the Commercial Contributor in,
+ the defense and any related settlement negotiations. The Indemnified
+ Contributor may participate in any such claim at its own expense.
+
+
For example, a Contributor might include the Program
+ in a commercial product offering, Product X. That Contributor is then a
+ Commercial Contributor. If that Commercial Contributor then makes performance
+ claims, or offers warranties related to Product X, those performance claims
+ and warranties are such Commercial Contributor's responsibility alone.
+ Under this section, the Commercial Contributor would have to defend claims
+ against the other Contributors related to those performance claims and
+ warranties, and if a court requires any other Contributor to pay any damages
+ as a result, the Commercial Contributor must pay those damages.
+
+
5. NO WARRANTY
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED
+ BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING,
+ WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+ solely responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement, including but not limited to the
+ risks and costs of program errors, compliance with applicable laws, damage
+ to or loss of data, programs or equipment, and unavailability or
+ interruption of operations.
+
+
6. DISCLAIMER OF LIABILITY
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED
+ BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY
+ LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS),
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+ GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+
7. GENERAL
+
If any provision of this Agreement is invalid or unenforceable under
+ applicable law, it shall not affect the validity or enforceability of the
+ remainder of the terms of this Agreement, and without further action by the
+ parties hereto, such provision shall be reformed to the minimum extent
+ necessary to make such provision valid and enforceable.
+
+
If Recipient institutes patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Program itself
+ (excluding combinations of the Program with other software or hardware)
+ infringes such Recipient's patent(s), then such Recipient's rights granted
+ under Section 2(b) shall terminate as of the date such litigation is filed.
+
+
All Recipient's rights under this Agreement shall terminate if it fails to
+ comply with any of the material terms or conditions of this Agreement and
+ does not cure such failure in a reasonable period of time after becoming
+ aware of such noncompliance. If all Recipient's rights under this Agreement
+ terminate, Recipient agrees to cease use and distribution of the Program
+ as soon as reasonably practicable. However, Recipient's obligations under
+ this Agreement and any licenses granted by Recipient relating to the
+ Program shall continue and survive.
+
+
Everyone is permitted to copy and distribute copies of this Agreement,
+ but in order to avoid inconsistency the Agreement is copyrighted and may
+ only be modified in the following manner. The Agreement Steward reserves
+ the right to publish new versions (including revisions) of this Agreement
+ from time to time. No one other than the Agreement Steward has the right
+ to modify this Agreement. The Eclipse Foundation is the initial Agreement
+ Steward. The Eclipse Foundation may assign the responsibility to serve as
+ the Agreement Steward to a suitable separate entity. Each new version of
+ the Agreement will be given a distinguishing version number. The Program
+ (including Contributions) may always be Distributed subject to the version
+ of the Agreement under which it was received. In addition, after a new
+ version of the Agreement is published, Contributor may elect to Distribute
+ the Program (including its Contributions) under the new version.
+
+
Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+ receives no rights or licenses to the intellectual property of any
+ Contributor under this Agreement, whether expressly, by implication,
+ estoppel or otherwise. All rights in the Program not expressly granted
+ under this Agreement are reserved. Nothing in this Agreement is intended
+ to be enforceable by any entity that is not a Contributor or Recipient.
+ No third-party beneficiary rights are created under this Agreement.
+
+
Exhibit A – Form of Secondary Licenses Notice
+
“This Source Code may also be made available under the following
+ Secondary Licenses when the conditions for such availability set forth
+ in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+ version(s), and exceptions or additional permissions here}.”
+
+
+
Simply including a copy of this Agreement, including this Exhibit A
+ is not sufficient to license the Source Code under Secondary Licenses.
+
+
If it is not possible or desirable to put the notice in a particular file,
+ then You may include the notice in a location (such as a LICENSE file in a
+ relevant directory) where a recipient would be likely to look for
+ such a notice.
+
+
You may add additional accurate notices of copyright ownership.
+
+
+
\ No newline at end of file
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/build.properties b/kura/org.eclipse.kura.rest.cloudconnection.provider/build.properties
new file mode 100644
index 00000000000..61baba2a407
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/build.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2023 Eurotech and/or its affiliates and others
+#
+# This program and the accompanying materials are made
+# available under the terms of the Eclipse Public License 2.0
+# which is available at https://www.eclipse.org/legal/epl-2.0/
+#
+# SPDX-License-Identifier: EPL-2.0
+#
+# Contributors:
+# Eurotech
+#
+output.. = target/classes
+bin.includes = .,\
+ META-INF/,\
+ OSGI-INF/,\
+ about.html,\
+ about_files/
+source.. = src/main/java/
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/pom.xml b/kura/org.eclipse.kura.rest.cloudconnection.provider/pom.xml
new file mode 100644
index 00000000000..59fe5435f9c
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+ 4.0.0
+
+
+ org.eclipse.kura
+ kura
+ 5.4.0
+
+
+
+ ${project.basedir}/..
+
+ ${project.basedir}/../test/*/target/site/jacoco-aggregate/jacoco.xml
+
+
+ org.eclipse.kura.rest.cloudconnection.provider
+ eclipse-plugin
+ 1.0.0
+
+
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionManagerBridge.java b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionManagerBridge.java
new file mode 100644
index 00000000000..5daec5c1c13
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionManagerBridge.java
@@ -0,0 +1,177 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.cloudconnection.provider;
+
+import static org.eclipse.kura.configuration.ConfigurationService.KURA_SERVICE_PID;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.kura.KuraConnectException;
+import org.eclipse.kura.KuraDisconnectException;
+import org.eclipse.kura.KuraErrorCode;
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.KuraRuntimeException;
+import org.eclipse.kura.cloud.CloudService;
+import org.eclipse.kura.cloudconnection.CloudConnectionManager;
+import org.eclipse.kura.data.DataService;
+import org.eclipse.kura.util.service.ServiceUtil;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("deprecation")
+public class CloudConnectionManagerBridge {
+
+ private static final String CONNECTION_ERROR_MESSAGE = "Error connecting. Please review your configuration.";
+
+ private static final Logger logger = LoggerFactory.getLogger(CloudConnectionManagerBridge.class);
+
+ private static final String DATA_SERVICE_REFERENCE_NAME = "DataService";
+
+ private final BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
+
+ public void connectCloudEndpoint(String connectionId) throws KuraException {
+
+ boolean ran = false;
+
+ ran = runOnDataService(connectionId, dataService -> {
+ int counter = 10;
+
+ dataService.connect();
+ while (!dataService.isConnected() && counter > 0) {
+ Thread.sleep(1000);
+ counter--;
+ }
+
+ });
+
+ ran = ran || runOnCloudConnectionManager(connectionId, cloudConnectionManager -> {
+ try {
+ cloudConnectionManager.connect();
+ } catch (KuraConnectException e) {
+ throw new KuraRuntimeException(KuraErrorCode.CONNECTION_FAILED, e, CONNECTION_ERROR_MESSAGE);
+ }
+ });
+
+ if (!ran) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ }
+
+ public void disconnectCloudEndpoint(String connectionId) throws KuraException {
+
+ boolean ran = false;
+
+ ran = runOnDataService(connectionId, dataService -> dataService.disconnect(10));
+
+ ran = ran || runOnCloudConnectionManager(connectionId, CloudConnectionManager::disconnect);
+
+ if (!ran) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+
+ }
+
+ public boolean isConnectedCloudEndpoint(String connectionId) throws KuraException {
+
+ boolean ran = false;
+
+ AtomicReference connectionStatusHolder = new AtomicReference<>(false);
+
+ ran = runOnDataService(connectionId, dataService -> connectionStatusHolder.set(dataService.isConnected()));
+
+ ran = ran || runOnCloudConnectionManager(connectionId,
+ cloudConnectionManager -> connectionStatusHolder.set(cloudConnectionManager.isConnected()));
+
+ if (!ran) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+
+ return connectionStatusHolder.get();
+
+ }
+
+ private boolean runOnDataService(String connectionId, InterruptableConsumer dataServiceConsumer)
+ throws KuraException {
+ Collection> cloudServiceReferences = ServiceUtil
+ .getServiceReferencesAsCollection(this.bundleContext, CloudService.class, null);
+
+ for (ServiceReference cloudServiceReference : cloudServiceReferences) {
+ String cloudServicePid = (String) cloudServiceReference.getProperty(KURA_SERVICE_PID);
+ if (cloudServicePid.endsWith(connectionId)) {
+ String dataServiceRef = (String) cloudServiceReference
+ .getProperty(DATA_SERVICE_REFERENCE_NAME + ComponentConstants.REFERENCE_TARGET_SUFFIX);
+ Collection> dataServiceReferences = ServiceUtil
+ .getServiceReferencesAsCollection(this.bundleContext, DataService.class, dataServiceRef);
+
+ for (ServiceReference dataServiceReference : dataServiceReferences) {
+ DataService dataService = ServiceUtil.getService(this.bundleContext, dataServiceReference);
+ if (dataService != null) {
+
+ invokeAndHandleExceptions(dataServiceConsumer, dataService);
+ return true;
+ }
+ ServiceUtil.ungetService(this.bundleContext, dataServiceReference);
+ }
+ }
+ ServiceUtil.ungetService(this.bundleContext, cloudServiceReference);
+ }
+
+ return false;
+ }
+
+ private boolean runOnCloudConnectionManager(String connectionId,
+ InterruptableConsumer cloudConnectionManagerConsumer) throws KuraException {
+ Collection> cloudConnectionManagerReferences = ServiceUtil
+ .getServiceReferencesAsCollection(this.bundleContext, CloudConnectionManager.class, null);
+
+ for (ServiceReference cloudConnectionManagerReference : cloudConnectionManagerReferences) {
+ String cloudConnectionManagerPid = (String) cloudConnectionManagerReference.getProperty(KURA_SERVICE_PID);
+ if (cloudConnectionManagerPid.endsWith(connectionId)) {
+ CloudConnectionManager cloudConnectionManager = ServiceUtil.getService(this.bundleContext,
+ cloudConnectionManagerReference);
+
+ invokeAndHandleExceptions(cloudConnectionManagerConsumer, cloudConnectionManager);
+
+ return true;
+ }
+ ServiceUtil.ungetService(this.bundleContext, cloudConnectionManagerReference);
+ }
+
+ return false;
+ }
+
+ private void invokeAndHandleExceptions(InterruptableConsumer consumer, T service) throws KuraException {
+ try {
+ consumer.accept(service);
+ } catch (KuraConnectException e) {
+ throw new KuraException(KuraErrorCode.CONNECTION_FAILED, e, CONNECTION_ERROR_MESSAGE);
+ } catch (IllegalStateException e) {
+ throw new KuraException(KuraErrorCode.INTERNAL_ERROR, e, "Illegal client state");
+ } catch (InterruptedException e) {
+ logger.warn("Interrupt Exception");
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public interface InterruptableConsumer {
+
+ void accept(T t)
+ throws InterruptedException, KuraConnectException, KuraDisconnectException, IllegalStateException;
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionRestService.java b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionRestService.java
new file mode 100644
index 00000000000..91c7144a16e
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionRestService.java
@@ -0,0 +1,288 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ ******************************************************************************/
+package org.eclipse.kura.internal.rest.cloudconnection.provider;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.kura.cloudconnection.request.RequestHandler;
+import org.eclipse.kura.cloudconnection.request.RequestHandlerRegistry;
+import org.eclipse.kura.configuration.ComponentConfiguration;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.crypto.CryptoService;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudComponentFactories;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudComponentInstances;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudConnectionFactoryPidAndCloudEndpointPid;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudEndpointPidRequest;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.ConnectedStatus;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.PidAndFactoryPidAndCloudEndpointPid;
+import org.eclipse.kura.request.handler.jaxrs.DefaultExceptionHandler;
+import org.eclipse.kura.request.handler.jaxrs.JaxRsRequestHandlerProxy;
+import org.eclipse.kura.rest.configuration.api.ComponentConfigurationList;
+import org.eclipse.kura.rest.configuration.api.DTOUtil;
+import org.eclipse.kura.rest.configuration.api.PidAndFactoryPid;
+import org.eclipse.kura.rest.configuration.api.PidSet;
+import org.eclipse.kura.rest.configuration.api.UpdateComponentConfigurationRequest;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.UserAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("cloudconnection/v1")
+public class CloudConnectionRestService {
+
+ private static final Logger logger = LoggerFactory.getLogger(CloudConnectionRestService.class);
+
+ private static final String MQTT_APP_ID = "CLD-V1";
+ private static final String REST_ROLE_NAME = "cloudconnection";
+ private static final String KURA_PERMISSION_REST_ROLE = "kura.permission.rest." + REST_ROLE_NAME;
+
+ private final RequestHandler requestHandler = new JaxRsRequestHandlerProxy(this);
+
+ private CloudConnectionService cloudConnectionService;
+ private CloudConnectionManagerBridge cloudConnectionManagerBridge;
+ private ConfigurationService configurationService;
+ private CryptoService cryptoService;
+
+ public void bindUserAdmin(UserAdmin userAdmin) {
+ userAdmin.createRole(KURA_PERMISSION_REST_ROLE, Role.GROUP);
+ }
+
+ public void bindCryptoService(CryptoService cryptoService) {
+ this.cryptoService = cryptoService;
+ }
+
+ public void bindRequestHandlerRegistry(RequestHandlerRegistry registry) {
+ try {
+ registry.registerRequestHandler(MQTT_APP_ID, this.requestHandler);
+ } catch (final Exception e) {
+ logger.warn("Failed to register {} request handler", MQTT_APP_ID, e);
+ }
+ }
+
+ public void bindConfigurationService(ConfigurationService configurationService) {
+ this.configurationService = configurationService;
+ }
+
+ public void unbindRequestHandlerRegistry(RequestHandlerRegistry registry) {
+ try {
+ registry.unregister(MQTT_APP_ID);
+ } catch (final Exception e) {
+ logger.warn("Failed to unregister {} request handler", MQTT_APP_ID, e);
+ }
+ }
+
+ public void activate() {
+ this.cloudConnectionService = new CloudConnectionService(this.configurationService);
+ this.cloudConnectionManagerBridge = new CloudConnectionManagerBridge();
+ }
+
+ @GET
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/instances")
+ @Produces(MediaType.APPLICATION_JSON)
+ public CloudComponentInstances findCloudComponentInstances() {
+ try {
+ return new CloudComponentInstances(this.cloudConnectionService.findCloudEndpointInstances(),
+ this.cloudConnectionService.findPubsubInstances());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint/stackComponentPids")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public PidSet getStackComponentsPids(
+ final CloudConnectionFactoryPidAndCloudEndpointPid cloudConnectionFactoryPidAndCloudEndpointPid) {
+ try {
+ Set pidSet = this.cloudConnectionService.getStackComponentsPids(
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudConnectionFactoryPid(),
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudEndpointPid());
+
+ return new PidSet(pidSet);
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response createCloudEndpoint(
+ final CloudConnectionFactoryPidAndCloudEndpointPid cloudConnectionFactoryPidAndCloudEndpointPid) {
+ try {
+ this.cloudConnectionService.createCloudEndpointFromFactory(
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudConnectionFactoryPid(),
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudEndpointPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @DELETE
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response deleteCloudEndpoint(
+ final CloudConnectionFactoryPidAndCloudEndpointPid cloudConnectionFactoryPidAndCloudEndpointPid) {
+ try {
+ this.cloudConnectionService.deleteCloudEndpointFromFactory(
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudConnectionFactoryPid(),
+ cloudConnectionFactoryPidAndCloudEndpointPid.getCloudEndpointPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @GET
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/factories")
+ @Produces(MediaType.APPLICATION_JSON)
+ public CloudComponentFactories getCloudComponentFactories() {
+ try {
+ return this.cloudConnectionService.getCloudComponentFactories();
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/pubSub")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response createPubSubInstance(
+ final PidAndFactoryPidAndCloudEndpointPid pidAndFactoryPidAndCloudEndpointPid) {
+ try {
+ this.cloudConnectionService.createPubSubInstance(pidAndFactoryPidAndCloudEndpointPid.getPid(),
+ pidAndFactoryPidAndCloudEndpointPid.getFactoryPid(),
+ pidAndFactoryPidAndCloudEndpointPid.getCloudEndpointPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @DELETE
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/pubSub")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response deletePubSubInstance(final PidAndFactoryPid pidAndFactoryPid) {
+ try {
+ this.cloudConnectionService.deletePubSubInstance(pidAndFactoryPid.getPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/configurations")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public ComponentConfigurationList getConfigurations(final PidSet pidSet) {
+ try {
+ List result = this.cloudConnectionService.getPubSubConfiguration(pidSet.getPids());
+
+ result.addAll(this.cloudConnectionService.getStackConfigurationsByPid(pidSet.getPids()));
+
+ return new ComponentConfigurationList(
+ result.stream().map(c -> DTOUtil.toComponentConfigurationDTO(c, this.cryptoService, false)
+ .replacePasswordsWithPlaceholder()).collect(Collectors.toList()));
+
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+ @PUT
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/configurations")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response updateStackComponentConfigurations(
+ UpdateComponentConfigurationRequest updateComponentConfigurationRequest) {
+ try {
+ this.cloudConnectionService.updateStackComponentConfiguration(
+ updateComponentConfigurationRequest.getComponentConfigurations(),
+ updateComponentConfigurationRequest.isTakeSnapshot());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint/connect")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response connectCloudEndpoint(CloudEndpointPidRequest cloudEndpointPid) {
+ try {
+ this.cloudConnectionManagerBridge.connectCloudEndpoint(cloudEndpointPid.getCloudEndpointPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint/disconnect")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response disconnectCloudEndpoint(CloudEndpointPidRequest cloudEndpointPid) {
+ try {
+ this.cloudConnectionManagerBridge.disconnectCloudEndpoint(cloudEndpointPid.getCloudEndpointPid());
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @RolesAllowed(REST_ROLE_NAME)
+ @Path("/cloudEndpoint/isConnected")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public ConnectedStatus isConnectedCloudEndpoint(CloudEndpointPidRequest cloudEndpointPid) {
+ try {
+ return new ConnectedStatus(
+ this.cloudConnectionManagerBridge.isConnectedCloudEndpoint(cloudEndpointPid.getCloudEndpointPid()));
+ } catch (Exception e) {
+ throw DefaultExceptionHandler.toWebApplicationException(e);
+ }
+ }
+
+}
diff --git a/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionService.java b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionService.java
new file mode 100644
index 00000000000..6c4d7511c05
--- /dev/null
+++ b/kura/org.eclipse.kura.rest.cloudconnection.provider/src/main/java/org/eclipse/kura/internal/rest/cloudconnection/provider/CloudConnectionService.java
@@ -0,0 +1,509 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Eurotech and/or its affiliates and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Eurotech
+ *******************************************************************************/
+package org.eclipse.kura.internal.rest.cloudconnection.provider;
+
+import static java.lang.String.format;
+import static org.eclipse.kura.configuration.ConfigurationService.KURA_SERVICE_PID;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.eclipse.kura.KuraErrorCode;
+import org.eclipse.kura.KuraException;
+import org.eclipse.kura.cloud.CloudService;
+import org.eclipse.kura.cloud.factory.CloudServiceFactory;
+import org.eclipse.kura.cloudconnection.CloudConnectionConstants;
+import org.eclipse.kura.cloudconnection.CloudConnectionManager;
+import org.eclipse.kura.cloudconnection.CloudEndpoint;
+import org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory;
+import org.eclipse.kura.cloudconnection.publisher.CloudPublisher;
+import org.eclipse.kura.cloudconnection.subscriber.CloudSubscriber;
+import org.eclipse.kura.configuration.ComponentConfiguration;
+import org.eclipse.kura.configuration.ConfigurationService;
+import org.eclipse.kura.core.configuration.ComponentConfigurationImpl;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudComponentFactories;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudConnectionFactoryInfo;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudConnectionState;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudEndpointInstance;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudEndpointType;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.CloudPubSubType;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.PubSubFactoryInfo;
+import org.eclipse.kura.internal.rest.cloudconnection.provider.dto.PubSubInstance;
+import org.eclipse.kura.rest.configuration.api.ComponentConfigurationDTO;
+import org.eclipse.kura.rest.configuration.api.DTOUtil;
+import org.eclipse.kura.util.service.ServiceUtil;
+import org.eclipse.kura.util.service.ServiceUtil.ServiceConsumer;
+import org.eclipse.kura.util.service.ServiceUtil.ServiceReferenceConsumer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.runtime.ServiceComponentRuntime;
+import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("deprecation")
+public class CloudConnectionService {
+
+ private static final Logger logger = LoggerFactory.getLogger(CloudConnectionService.class);
+
+ private static final String CLOUD_CONNECTION_FACTORY_FILTER = "(" + "|"
+ + "(objectClass=org.eclipse.kura.cloudconnection.factory.CloudConnectionFactory)"
+ + "(objectClass=org.eclipse.kura.cloud.factory.CloudServiceFactory)" + ")";
+
+ private static final String KURA_UI_CSF_PID_DEFAULT = "kura.ui.csf.pid.default";
+ private static final String KURA_UI_CSF_PID_REGEX = "kura.ui.csf.pid.regex";
+
+ private static final String CLOUD_PUBLISHER = CloudPublisher.class.getName();
+ private static final String CLOUD_SUBSCRIBER = CloudSubscriber.class.getName();
+
+ private final BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
+
+ private final ConfigurationService configurationService;
+
+ public CloudConnectionService(ConfigurationService configurationService) {
+ this.configurationService = configurationService;
+ }
+
+ public List findCloudEndpointInstances() throws KuraException {
+
+ final List result = new ArrayList<>();
+
+ withAllCloudConnectionFactories(service -> {
+
+ final String factoryPid = service.getFactoryPid();
+ if (factoryPid == null) {
+ return;
+ }
+
+ for (final String pid : service.getManagedCloudConnectionPids()) {
+ if (pid == null) {
+ continue;
+ }
+
+ final CloudEndpointInstance cloudConnectionEntry = new CloudEndpointInstance(factoryPid, pid);
+
+ fillState(cloudConnectionEntry);
+
+ result.add(cloudConnectionEntry);
+ }
+
+ });
+
+ return result;
+ }
+
+ public List findPubsubInstances() throws KuraException {
+ final List result = new ArrayList<>();
+
+ result.addAll(getPubSubInstances(CloudPubSubType.PUBLISHER));
+ result.addAll(getPubSubInstances(CloudPubSubType.SUBSCRIBER));
+
+ return result;
+ }
+
+ public Set getStackComponentsPids(final String factoryPid, final String cloudEndpointPid)
+ throws KuraException {
+
+ final Set result = new HashSet<>();
+
+ withAllCloudConnectionFactories(factory -> {
+ if (factoryPid.equals(factory.getFactoryPid())) {
+ if (!factory.getManagedCloudConnectionPids().contains(cloudEndpointPid)) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ result.addAll(getStackComponentPids(factory, cloudEndpointPid));
+ }
+ });
+
+ return result;
+ }
+
+ private List getStackComponentPids(CloudConnectionFactory factory, String pid) {
+
+ List result = new ArrayList<>();
+
+ try {
+ result = factory.getStackComponentsPids(pid);
+ } catch (Exception e) {
+ // nothing to do, just return an empty list
+ }
+
+ return result;
+ }
+
+ public Set getStackConfigurationsByPid(final Set pids) throws KuraException {
+
+ List result = new ArrayList<>();
+
+ Set resultSet = new HashSet<>();
+
+ withAllCloudConnectionFactories(factory -> {
+
+ Set managedCloudConnectionPids = factory.getManagedCloudConnectionPids();
+
+ for (String cloudConnectionPid : managedCloudConnectionPids) {
+ List stackComponentsPids = getStackComponentPids(factory, cloudConnectionPid);
+
+ pids.stream().filter(stackComponentsPids::contains).forEach(result::add);
+ }
+
+ });
+
+ for (String stackConfigurationPid : result) {
+ ComponentConfiguration componetConfiguration = this.configurationService
+ .getComponentConfiguration(stackConfigurationPid);
+ if (componetConfiguration != null) {
+ resultSet.add(componetConfiguration);
+ }
+ }
+
+ return resultSet;
+ }
+
+ public void createCloudEndpointFromFactory(String factoryPid, String cloudServicePid) throws KuraException {
+ if (factoryPid == null || factoryPid.trim().isEmpty() || cloudServicePid == null
+ || cloudServicePid.trim().isEmpty()) {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST);
+ }
+
+ AtomicReference found = new AtomicReference<>(false);
+
+ withAllCloudConnectionFactories(service -> {
+ if (service.getFactoryPid().equals(factoryPid)) {
+ found.set(true);
+ service.createConfiguration(cloudServicePid);
+ }
+ });
+
+ if (Boolean.FALSE.equals(found.get())) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ }
+
+ public void deleteCloudEndpointFromFactory(String factoryPid, String cloudEndpointPid) throws KuraException {
+ if (factoryPid == null || factoryPid.trim().isEmpty() || cloudEndpointPid == null
+ || cloudEndpointPid.trim().isEmpty()) {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST);
+ }
+
+ AtomicReference found = new AtomicReference<>(false);
+
+ withAllCloudConnectionFactories(factory -> {
+ if (factory.getFactoryPid().equals(factoryPid)
+ && factory.getManagedCloudConnectionPids().contains(cloudEndpointPid)) {
+
+ factory.deleteConfiguration(cloudEndpointPid);
+ found.set(true);
+ }
+ });
+
+ if (Boolean.FALSE.equals(found.get())) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ }
+
+ public CloudComponentFactories getCloudComponentFactories() throws KuraException {
+ final List cloudConnectionFactoryPids = new ArrayList<>();
+
+ withAllCloudConnectionFactoryRefs((ref, ctx) -> {
+
+ try {
+ final CloudConnectionFactory service = wrap(ctx.getService(ref));
+ String defaultPid = (String) ref.getProperty(KURA_UI_CSF_PID_DEFAULT);
+ String pidRegex = (String) ref.getProperty(KURA_UI_CSF_PID_REGEX);
+
+ CloudConnectionFactoryInfo cloudConnectionFactoryInfoDTO = new CloudConnectionFactoryInfo(
+ service.getFactoryPid(), defaultPid, pidRegex);
+
+ cloudConnectionFactoryPids.add(cloudConnectionFactoryInfoDTO);
+ } finally {
+ ctx.ungetService(ref);
+ }
+
+ });
+
+ final List pubSubFactories = getPubSubFactories();
+
+ return new CloudComponentFactories(cloudConnectionFactoryPids, pubSubFactories);
+ }
+
+ public void createPubSubInstance(final String pid, final String factoryPid, final String cloudEndpointPid)
+ throws KuraException {
+
+ if (pid == null || pid.trim().isEmpty() || //
+ factoryPid == null || factoryPid.trim().isEmpty() //
+ || cloudEndpointPid == null || cloudEndpointPid.trim().isEmpty()) {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST);
+ }
+
+ requireIsPubSubFactory(factoryPid);
+
+ ServiceUtil.applyToServiceOptionally(this.bundleContext, ConfigurationService.class, cs -> {
+ cs.createFactoryConfiguration(factoryPid, pid, Collections.singletonMap(
+ CloudConnectionConstants.CLOUD_ENDPOINT_SERVICE_PID_PROP_NAME.value(), cloudEndpointPid), true);
+
+ return null;
+ });
+ }
+
+ public void deletePubSubInstance(final String pid) throws KuraException {
+
+ requireIsPubSub(pid);
+
+ ServiceUtil.applyToServiceOptionally(this.bundleContext, ConfigurationService.class, cs -> {
+ cs.deleteFactoryConfiguration(pid, true);
+
+ return null;
+ });
+ }
+
+ public List getPubSubConfiguration(Set pids) throws KuraException {
+
+ List result = new ArrayList<>();
+
+ for (String pid : pids) {
+ if (isPubSub(pid)) {
+ ComponentConfiguration configuration = this.configurationService.getComponentConfiguration(pid);
+ if (configuration != null) {
+ result.add(configuration);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void updateStackComponentConfiguration(List componentConfigurations,
+ boolean takeSnapshot) throws KuraException {
+
+ for (ComponentConfigurationDTO componentConfig : componentConfigurations) {
+ if (!(isPubSub(componentConfig.getPid()) || isComponentManagedByFactory(componentConfig.getPid()))) {
+ throw new KuraException(KuraErrorCode.BAD_REQUEST);
+ }
+ }
+
+ updateComponentConfigurations(componentConfigurations, takeSnapshot);
+
+ }
+
+ private void updateComponentConfigurations(List componentConfigurations,
+ boolean takeSnapshot) throws KuraException {
+
+ List configs = componentConfigurations.stream()
+ .map(cc -> new ComponentConfigurationImpl(cc.getPid(), null,
+ DTOUtil.dtosToConfigurationProperties(cc.getProperties())))
+ .collect(Collectors.toList());
+
+ this.configurationService.updateConfigurations(configs, takeSnapshot);
+ }
+
+ private boolean isComponentManagedByFactory(final String pid) {
+ final AtomicBoolean result = new AtomicBoolean(false);
+
+ try {
+ withAllCloudConnectionFactories(f -> {
+ for (final String stackPid : f.getManagedCloudConnectionPids()) {
+ if (f.getStackComponentsPids(stackPid).contains(pid)) {
+ result.set(true);
+ return;
+ }
+ }
+ });
+ } catch (final Exception e) {
+ return false;
+ }
+
+ return result.get();
+ }
+
+ private void requireIsPubSub(final String pid) throws KuraException {
+ if (!isPubSub(pid)) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ }
+
+ private boolean isPubSub(final String pid) {
+ return ServiceUtil.providesService(this.bundleContext, pid, CloudPublisher.class)
+ || ServiceUtil.providesService(this.bundleContext, pid, CloudSubscriber.class);
+ }
+
+ private List getPubSubFactories() throws KuraException {
+
+ return ServiceUtil.applyToServiceOptionally(this.bundleContext, ServiceComponentRuntime.class, scr ->
+
+ scr.getComponentDescriptionDTOs().stream().map(this::pubSubFactoryToInfo).filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ }
+
+ private void requireIsPubSubFactory(final String factoryPid) throws KuraException {
+ final boolean isPubSub = ServiceUtil.applyToServiceOptionally(this.bundleContext, ServiceComponentRuntime.class,
+ scr -> scr.getComponentDescriptionDTOs().stream().anyMatch(c -> {
+ final Map properties = c.properties;
+
+ if (properties == null) {
+ return false;
+ }
+
+ return Objects.equals(factoryPid, properties.get("service.pid")) && pubSubFactoryToInfo(c) != null;
+ }));
+
+ if (!isPubSub) {
+ throw new KuraException(KuraErrorCode.NOT_FOUND);
+ }
+ }
+
+ private PubSubFactoryInfo pubSubFactoryToInfo(final ComponentDescriptionDTO component) {
+
+ if (Arrays.stream(component.serviceInterfaces)
+ .noneMatch(intf -> CLOUD_PUBLISHER.equals(intf) || CLOUD_SUBSCRIBER.equals(intf))) {
+ return null;
+ }
+
+ final String ccsfFactoryPidPropName = CloudConnectionConstants.CLOUD_CONNECTION_FACTORY_PID_PROP_NAME.value();
+
+ final Object ccsfFactoryPid = component.properties.get(ccsfFactoryPidPropName);
+ final Object factoryPid = component.properties.get("service.pid");
+ final Object defaultFactoryPid = component.properties.get(KURA_UI_CSF_PID_DEFAULT);
+ final Object defaultFactoryPidRegex = component.properties.get(KURA_UI_CSF_PID_REGEX);
+
+ if (!(factoryPid instanceof String)) {
+ logger.warn(
+ "component {} defines a CloudPublisher or CloudSubscriber but does not specify the service.pid property, ignoring it",
+ component.name);
+ return null;
+ }
+
+ if (!(ccsfFactoryPid instanceof String)) {
+ logger.warn(
+ "component {} defines a CloudPublisher or CloudSubscriber but does not specify the {} property, ignoring it",
+ component.name, ccsfFactoryPidPropName);
+ return null;
+ }
+
+ return new PubSubFactoryInfo((String) factoryPid, (String) ccsfFactoryPid, (String) defaultFactoryPid,
+ (String) defaultFactoryPidRegex);
+ }
+
+ private void fillState(final CloudEndpointInstance cloudEndpointInstance) throws KuraException {
+
+ cloudEndpointInstance.setState(CloudConnectionState.UNREGISTERED);
+
+ final String filter = format("(%s=%s)", KURA_SERVICE_PID, cloudEndpointInstance.getCloudEndpointPid());
+
+ ServiceUtil.withAllServices(this.bundleContext, null, filter, service -> {
+ if (service instanceof CloudConnectionManager) {
+ cloudEndpointInstance
+ .setState(((CloudConnectionManager) service).isConnected() ? CloudConnectionState.CONNECTED
+ : CloudConnectionState.DISCONNECTED);
+ cloudEndpointInstance.setConnectionType(CloudEndpointType.CLOUD_CONNECTION_MANAGER);
+ } else if (service instanceof CloudEndpoint) {
+ cloudEndpointInstance.setConnectionType(CloudEndpointType.CLOUD_ENDPOINT);
+ } else if (service instanceof CloudService) {
+ cloudEndpointInstance.setState(((CloudService) service).isConnected() ? CloudConnectionState.CONNECTED
+ : CloudConnectionState.DISCONNECTED);
+ cloudEndpointInstance.setConnectionType(CloudEndpointType.CLOUD_CONNECTION_MANAGER);
+ }
+ });
+ }
+
+ private void withAllCloudConnectionFactoryRefs(final ServiceReferenceConsumer