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);