From fe7cccd5e762fb2b538b1fd6e4f3dba56a3ce9e4 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 22 Jan 2020 16:14:38 -0800 Subject: [PATCH] Synchronize cf editor with boot dash cf targets --- .../META-INF/MANIFEST.MF | 6 +- .../ls/BootDashTargetInfoSynchronizer.java | 150 ++++++++++++++++++ .../CloudFoundryManifestLanguageServer.java | 7 +- 3 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/BootDashTargetInfoSynchronizer.java diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF index e004e8725..626db8be5 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/META-INF/MANIFEST.MF @@ -17,7 +17,11 @@ Require-Bundle: org.eclipse.jdt.launching;bundle-version="3.8.0", org.eclipse.tm4e.core;bundle-version="0.1.0", org.eclipse.tm4e.ui;bundle-version="0.1.0", org.eclipse.ui, - org.eclipse.ui.editors + org.eclipse.ui.editors, + org.springframework.ide.eclipse.boot.dash, + org.springsource.ide.eclipse.commons.livexp, + io.projectreactor.reactor-core, + org.reactivestreams.reactive-streams Import-Package: org.eclipse.jface.preference, org.osgi.framework Export-Package: org.springframework.tooling.cloudfoundry.manifest.ls diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/BootDashTargetInfoSynchronizer.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/BootDashTargetInfoSynchronizer.java new file mode 100644 index 000000000..bffd8eada --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/BootDashTargetInfoSynchronizer.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2015, 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.cloudfoundry.manifest.ls; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.ide.eclipse.boot.dash.BootDashActivator; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CfTargetsInfo; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CfTargetsInfo.Target; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CfTargetsInfo.TargetDiagnosticMessages; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryRunTarget; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryTargetProperties; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.ClientRequests; +import org.springframework.ide.eclipse.boot.dash.cloudfoundry.client.v2.DefaultClientRequestsV2; +import org.springframework.ide.eclipse.boot.dash.model.BootDashViewModel; +import org.springframework.ide.eclipse.boot.dash.model.RunTarget; +import org.springsource.ide.eclipse.commons.livexp.core.ValueListener; + +import com.google.common.collect.ImmutableSet; + +/** + * Helper methods to attach listener to the BootDash model and keep editor in sync with + * all connected cloudfoundry targets. + *

+ * Usage: just call the 'start()' method once, at the first time the information is desired. + */ +public class BootDashTargetInfoSynchronizer { + + private static AtomicBoolean started = new AtomicBoolean(false); + + public static void start() { + if (started.compareAndSet(false, true)) { + model().getRunTargets().addListener((e,v) -> { + // On target changes, add the client listener in each target so that when the client changes, a notification is sent + addClientChangeListeners(e.getValue()); + }); + } + } + + private static final ValueListener clientsChangedListener = (exp, client) -> { + if (client instanceof DefaultClientRequestsV2) { + addRefreshTokenListener((DefaultClientRequestsV2) client); + } + }; + + /** + * Add a listener to be notified when the refresh token becomes available OR + * changes + */ + static private void addRefreshTokenListener(DefaultClientRequestsV2 client) { + if (client != null && client.getRefreshTokens() != null && model() != null + && model().getRunTargets() != null) { + client.getRefreshTokens().doOnNext((token) -> + // Although the refresh token change is for ONE client (i.e. one target) + // compute cloud target information for ALL currently connected targets + // as the manifest editor is updated with the full up-to-date list of connected + // boot dash targets + updateCloudTargetsInManifestEditor(model().getRunTargets().getValue()) + ).subscribe(); + client.getRefreshTokens().doOnComplete(() -> + updateCloudTargetsInManifestEditor(model().getRunTargets().getValue()) + ).subscribe(); + } + } + + static private void updateCloudTargetsInManifestEditor(ImmutableSet value) { + Set toUpdate = value == null ? ImmutableSet.of() : value; + + CfTargetsInfo targetsInfo = asTargetsInfo(toUpdate); + CloudFoundryManifestLanguageServer.setCfTargetLoginOptions(targetsInfo); + } + + private static BootDashViewModel model() { + return BootDashActivator.getDefault().getModel(); + } + + private static void addClientChangeListeners(ImmutableSet targets) { + if (targets != null) { + for (RunTarget runTarget : targets) { + if (runTarget instanceof CloudFoundryRunTarget) { + ((CloudFoundryRunTarget) runTarget).addConnectionStateListener(clientsChangedListener); + } + } + } + } + + private static CfTargetsInfo asTargetsInfo(Collection targets) { + List collectedTargets = new ArrayList<>(); + for (RunTarget runTarget : targets) { + if (runTarget instanceof CloudFoundryRunTarget) { + + CloudFoundryRunTarget cloudFoundryRunTarget = (CloudFoundryRunTarget) runTarget; + if (cloudFoundryRunTarget.isConnected()) { + String token = cloudFoundryRunTarget.getClient().getRefreshToken(); + if (token != null) { + CloudFoundryTargetProperties properties = cloudFoundryRunTarget.getTargetProperties(); + String target = properties.getUrl(); + String org = properties.getOrganizationName(); + String space = properties.getSpaceName(); + boolean sslDisabled = properties.skipSslValidation(); + + CfTargetsInfo.Target integrationTarget = new Target(); + + integrationTarget.setApi(target); + integrationTarget.setOrg(org); + integrationTarget.setSpace(space); + integrationTarget.setSslDisabled(sslDisabled); + integrationTarget.setRefreshToken(token); + collectedTargets.add(integrationTarget); + } + } + } + } + + CfTargetsInfo targetsInfo = new CfTargetsInfo(); + targetsInfo.setCfTargets(collectedTargets); + targetsInfo.setDiagnosticMessages(getDiagnosticMessages()); + return targetsInfo ; + } + + // NOTE: using ':' to separate the "shorter" part of the message from the longer. The longer part may be shown in the UI by expanding the hover info + private static final String TARGET_SOURCE = "Boot Dashboard"; + private static final String NO_ORG_SPACE = "Boot Dashboard - No org/space selected: Verify Cloud Foundry target connection in Boot Dashboard or login via 'cf' CLI"; + // Make this a "generic" message, instead of using "Boot Dash" prefix as it shows general instructions when there are not targets + private static final String NO_TARGETS = "No Cloud Foundry targets found: Create a target in Boot Dashboard or login via 'cf' CLI"; + private static final String CONNECTION_ERROR = "Boot Dashboard - Error connecting to Cloud Foundry target: Verify network connection or that existing target has valid credentials."; + + private static TargetDiagnosticMessages getDiagnosticMessages() { + TargetDiagnosticMessages messages = new TargetDiagnosticMessages(); + messages.setConnectionError(CONNECTION_ERROR); + messages.setNoOrgSpace(NO_ORG_SPACE); + messages.setNoTargetsFound(NO_TARGETS); + messages.setTargetSource(TARGET_SOURCE); + return messages; + } + + +} diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java index a341e9907..dcedbf5af 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java @@ -25,6 +25,8 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage; import org.eclipse.lsp4j.services.LanguageServer; import org.springframework.tooling.ls.eclipse.commons.STS4LanguageServerProcessStreamConnector; +import com.google.common.collect.ImmutableSet; + /** * @author Martin Lippert */ @@ -84,7 +86,8 @@ public class CloudFoundryManifestLanguageServer extends STS4LanguageServerProces @Override public Object getInitializationOptions(URI rootUri) { - return cfTargetOptionSettings; + Object opts = cfTargetOptionSettings; + return opts!=null?opts:ImmutableSet.of(); } protected void updateLanguageServer() { @@ -93,8 +96,10 @@ public class CloudFoundryManifestLanguageServer extends STS4LanguageServerProces } private static void addLanguageServer(CloudFoundryManifestLanguageServer server) { + BootDashTargetInfoSynchronizer.start(); servers.add(server); } + private static void removeLanguageServer(CloudFoundryManifestLanguageServer server) { servers.remove(server);