From 276139bad7af107dfb1d3bb8868266dd98010ba7 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Mon, 15 Jul 2019 19:18:18 +0200 Subject: [PATCH] Extend GLSP Protocol with "initialize" method - Allow client to send an initialize with parameters to server - Extend server to handle initialize and the initialize options - Add example options that are logged to the Workflow example --- .../workflow-glsp-client-contribution.ts | 12 ++++++ .../language/glsp-client-contribution.ts | 32 +++++++++++++++- .../browser/language/glsp-client-services.ts | 5 ++- .../src/browser/language/glsp-client.ts | 9 +++++ .../theia-integration/src/common/protocol.ts | 11 +++++- .../example/workflow/WorkflowGLSPModule.java | 7 ++++ .../example/workflow/WorkflowGLSPServer.java | 38 +++++++++++++++++++ .../workflow/WorkflowInitializeOptions.java | 31 +++++++++++++++ .../glsp/api/jsonrpc/GLSPServer.java | 3 +- .../api/jsonrpc/InitializeParameters.java | 38 +++++++++++++++++++ .../server/jsonrpc/DefaultGLSPServer.java | 27 ++++++++++++- 11 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPServer.java create mode 100644 server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowInitializeOptions.java create mode 100644 server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/InitializeParameters.java diff --git a/client/examples/workflow/workflow-theia/src/browser/language/workflow-glsp-client-contribution.ts b/client/examples/workflow/workflow-theia/src/browser/language/workflow-glsp-client-contribution.ts index 16b62174..936fe0e0 100644 --- a/client/examples/workflow/workflow-theia/src/browser/language/workflow-glsp-client-contribution.ts +++ b/client/examples/workflow/workflow-theia/src/browser/language/workflow-glsp-client-contribution.ts @@ -18,9 +18,21 @@ import { injectable } from "inversify"; import { WorkflowLanguage } from "../../common/workflow-language"; +export interface WorkflowInitializeOptions { + timestamp: Date, + message: string +} + @injectable() export class WorkflowGLSPClientContribution extends BaseGLSPClientContribution { readonly id = WorkflowLanguage.Id; readonly name = WorkflowLanguage.Name; readonly fileExtensions = [WorkflowLanguage.FileExtension]; + + protected createInitializeOptions(): WorkflowInitializeOptions { + return { + timestamp: new Date(), + message: "Custom Options Available" + }; + } } diff --git a/client/packages/theia-integration/src/browser/language/glsp-client-contribution.ts b/client/packages/theia-integration/src/browser/language/glsp-client-contribution.ts index fdb0725f..cd1c0708 100644 --- a/client/packages/theia-integration/src/browser/language/glsp-client-contribution.ts +++ b/client/packages/theia-integration/src/browser/language/glsp-client-contribution.ts @@ -24,6 +24,7 @@ import { inject, injectable, multiInject } from "inversify"; import { DiagramManagerProvider } from "sprotty-theia/lib"; import { MessageConnection, ResponseError } from "vscode-jsonrpc"; +import { InitializeParameters } from "../../common"; import { GLSPClientFactory } from "./glsp-client"; import { GLSPClient, GLSPClientOptions } from "./glsp-client-services"; @@ -105,16 +106,44 @@ export abstract class BaseGLSPClientContribution implements GLSPClientContributi })); } protected readonly toDeactivate = new DisposableCollection(); + activate(): Disposable { if (this.toDeactivate.disposed) { - this.doActivate(this.toDeactivate); + this.doActivate(this.toDeactivate) + .then(() => this.initialize()); } return this.toDeactivate; } + deactivate(): void { this.toDeactivate.dispose(); } + protected createInitializeParameters(): InitializeParameters { + const initOptions = this.createInitializeOptions(); + return initOptions ? { options: initOptions } : {}; + } + + protected createInitializeOptions(): any { + return undefined; + } + + initialize(): void { + const parameters = this.createInitializeParameters(); + this.glspClient.then(client => client.initialize(parameters) + .then(success => { + if (!success) { + this.messageService.error(`Failed to initialize ${this.name} glsp server with ${JSON.stringify(parameters)}`, 'Retry') + .then(retry => { + if (retry) { + this.initialize(); + } + }); + } + }) + ); + } + protected async doActivate(toDeactivate: DisposableCollection): Promise { const options: WebSocketOptions = {}; toDeactivate.push(Disposable.create(() => options.reconnecting = false)); @@ -174,7 +203,6 @@ export abstract class BaseGLSPClientContribution implements GLSPClientContributi protected createOptions(): GLSPClientOptions { return { initializationFailedHandler: err => this.handleInitializationFailed(err), - }; } diff --git a/client/packages/theia-integration/src/browser/language/glsp-client-services.ts b/client/packages/theia-integration/src/browser/language/glsp-client-services.ts index 40bb7764..67e33098 100644 --- a/client/packages/theia-integration/src/browser/language/glsp-client-services.ts +++ b/client/packages/theia-integration/src/browser/language/glsp-client-services.ts @@ -23,7 +23,7 @@ import { } from "@theia/languages/lib/browser"; import { Disposable, Message, MessageConnection, NotificationHandler, NotificationType } from "vscode-jsonrpc"; -import { ExitNotification, ShutdownRequest } from "../../common"; +import { ExitNotification, InitializeParameters, InitializeRequest, ShutdownRequest } from "../../common"; export const GLSPClient = Symbol.for('GLSPClient'); @@ -31,6 +31,7 @@ export const GLSPClient = Symbol.for('GLSPClient'); export interface GLSPClient { onReady(): Promise start(): Disposable; + initialize(params: InitializeParameters): Thenable; stop(): Thenable | undefined onNotification(type: NotificationType, handler: NotificationHandler

): void; sendNotification(type: NotificationType, params?: P): void; @@ -57,6 +58,7 @@ export interface Connection { listen(): void onNotification(type: NotificationType, handler: NotificationHandler

): void; sendNotification(type: NotificationType, params?: P): void; + initialize(params: InitializeParameters): Thenable; shutdown(): Thenable; exit(): void; dispose(): void; @@ -76,6 +78,7 @@ export function createConnection(connection: MessageConnection, errorHandler: Co listen: () => connection.listen(), sendNotification: (type: NotificationType, params?: P): void => connection.sendNotification(type, params), onNotification: (type: NotificationType, handler: NotificationHandler

): void => connection.onNotification(type, handler), + initialize: (params: InitializeParameters) => connection.sendRequest(InitializeRequest.type, params), shutdown: () => connection.sendRequest(ShutdownRequest.type, undefined), exit: () => connection.sendNotification(ExitNotification.type), dispose: () => connection.dispose() diff --git a/client/packages/theia-integration/src/browser/language/glsp-client.ts b/client/packages/theia-integration/src/browser/language/glsp-client.ts index 6d7b69c5..7f9144d0 100644 --- a/client/packages/theia-integration/src/browser/language/glsp-client.ts +++ b/client/packages/theia-integration/src/browser/language/glsp-client.ts @@ -21,6 +21,7 @@ import { LanguageContribution } from "@theia/languages/lib/common"; import { inject, injectable } from "inversify"; import { MessageConnection, NotificationType } from "vscode-jsonrpc"; +import { InitializeParameters } from "../../common"; import { Connection, ConnectionProvider, createConnection, GLSPClient, GLSPClientOptions } from "./glsp-client-services"; enum ClientState { @@ -53,6 +54,7 @@ export class BaseGLSPClient implements GLSPClient { this.state = ClientState.Initial; } + start(): Disposable { this.state = ClientState.Starting; this.resolveConnection().then((connection) => { @@ -68,6 +70,13 @@ export class BaseGLSPClient implements GLSPClient { } + initialize(params: InitializeParameters): Thenable { + if (this.connectionPromise && this.resolvedConnection) { + return this.resolvedConnection.initialize(params); + } + return Promise.resolve(false); + } + public stop(): Thenable | undefined { if (!this.connectionPromise) { this.state = ClientState.Stopped; diff --git a/client/packages/theia-integration/src/common/protocol.ts b/client/packages/theia-integration/src/common/protocol.ts index d9dd67cc..13d66ed9 100644 --- a/client/packages/theia-integration/src/common/protocol.ts +++ b/client/packages/theia-integration/src/common/protocol.ts @@ -14,12 +14,21 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ import { ActionMessage } from "@glsp/sprotty-client/lib"; -import { NotificationType, NotificationType0, RequestType0 } from "vscode-jsonrpc"; +import { NotificationType, NotificationType0, RequestType, RequestType0 } from "vscode-jsonrpc"; + + +export interface InitializeParameters { + options?: any +} export namespace ActionMessageNotification { export const type = new NotificationType('process'); } +export namespace InitializeRequest { + export const type = new RequestType('initialize'); +} + export namespace ShutdownRequest { export const type = new RequestType0('shutdown'); } diff --git a/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPModule.java b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPModule.java index 8d294e3e..3ad4eeda 100644 --- a/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPModule.java +++ b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPModule.java @@ -23,6 +23,7 @@ import com.eclipsesource.glsp.api.factory.PopupModelFactory; import com.eclipsesource.glsp.api.handler.OperationHandler; import com.eclipsesource.glsp.api.handler.ServerCommandHandler; +import com.eclipsesource.glsp.api.jsonrpc.GLSPServer; import com.eclipsesource.glsp.api.labeledit.LabelEditValidator; import com.eclipsesource.glsp.api.markers.ModelValidator; import com.eclipsesource.glsp.api.model.ModelElementOpenListener; @@ -43,6 +44,7 @@ import com.eclipsesource.glsp.example.workflow.marker.WorkflowModelValidator; import com.eclipsesource.glsp.graph.GraphExtension; import com.eclipsesource.glsp.server.di.DefaultGLSPModule; +import com.eclipsesource.glsp.server.jsonrpc.DefaultGLSPServer; import com.eclipsesource.glsp.server.operationhandler.ApplyLabelEditOperationHandler; import com.eclipsesource.glsp.server.operationhandler.ChangeBoundsOperationHandler; import com.eclipsesource.glsp.server.operationhandler.DeleteOperationHandler; @@ -50,6 +52,11 @@ @SuppressWarnings("serial") public class WorkflowGLSPModule extends DefaultGLSPModule { + @Override + protected Class bindGLSPServer() { + return WorkflowGLSPServer.class; + } + @Override public Class bindPopupModelFactory() { return WorkflowPopupFactory.class; diff --git a/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPServer.java b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPServer.java new file mode 100644 index 00000000..29e85e20 --- /dev/null +++ b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowGLSPServer.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * 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: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ******************************************************************************/ +package com.eclipsesource.glsp.example.workflow; + +import java.util.concurrent.CompletableFuture; + +import org.apache.log4j.Logger; + +import com.eclipsesource.glsp.server.jsonrpc.DefaultGLSPServer; + +public class WorkflowGLSPServer extends DefaultGLSPServer { + static Logger log = Logger.getLogger(WorkflowGLSPServer.class); + + public WorkflowGLSPServer() { + super(WorkflowInitializeOptions.class); + } + + @Override + public CompletableFuture handleOptions(WorkflowInitializeOptions options) { + if(options != null) { + log.debug(options.getTimestamp() + ": " + options.getMessage()); + } + return CompletableFuture.completedFuture(true); + } +} diff --git a/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowInitializeOptions.java b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowInitializeOptions.java new file mode 100644 index 00000000..a11e2b49 --- /dev/null +++ b/server/example/workflow-example/src/main/java/com/eclipsesource/glsp/example/workflow/WorkflowInitializeOptions.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * 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: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ******************************************************************************/ +package com.eclipsesource.glsp.example.workflow; + +import java.util.Date; + +public class WorkflowInitializeOptions { + private Date timestamp; + private String message; + + public Date getTimestamp() { + return timestamp; + } + + public String getMessage() { + return message; + } +} diff --git a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/GLSPServer.java b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/GLSPServer.java index 8c5018cd..978f5285 100644 --- a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/GLSPServer.java +++ b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/GLSPServer.java @@ -29,7 +29,8 @@ public interface Provider { GLSPServer getGraphicalLanguageServer(String clientId); } - void initialize(); + @JsonRequest("initialize") + CompletableFuture initialize(InitializeParameters params); @JsonNotification("process") void process(ActionMessage message); diff --git a/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/InitializeParameters.java b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/InitializeParameters.java new file mode 100644 index 00000000..e64409af --- /dev/null +++ b/server/glsp-api/src/main/java/com/eclipsesource/glsp/api/jsonrpc/InitializeParameters.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2019 EclipseSource and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * 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: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ******************************************************************************/ +package com.eclipsesource.glsp.api.jsonrpc; + +import org.eclipse.lsp4j.jsonrpc.json.adapters.JsonElementTypeAdapter; + +import com.google.gson.annotations.JsonAdapter; + +public class InitializeParameters { + @JsonAdapter(JsonElementTypeAdapter.Factory.class) + private Object options; + + public Object getOptions() { + return options; + } + + public void setOptions(Object options) { + this.options = options; + } + + @Override + public String toString() { + return "InitializeParameters[options = " + options + "]"; + } +} diff --git a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/jsonrpc/DefaultGLSPServer.java b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/jsonrpc/DefaultGLSPServer.java index 90c1bf61..513a9aff 100644 --- a/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/jsonrpc/DefaultGLSPServer.java +++ b/server/glsp-server/src/main/java/com/eclipsesource/glsp/server/jsonrpc/DefaultGLSPServer.java @@ -27,12 +27,15 @@ import com.eclipsesource.glsp.api.action.kind.IdentifiableResponseAction; import com.eclipsesource.glsp.api.jsonrpc.GLSPClient; import com.eclipsesource.glsp.api.jsonrpc.GLSPServer; +import com.eclipsesource.glsp.api.jsonrpc.InitializeParameters; import com.eclipsesource.glsp.api.model.ModelStateProvider; import com.eclipsesource.glsp.api.types.ServerStatus; import com.eclipsesource.glsp.api.types.ServerStatus.Severity; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.inject.Inject; -public class DefaultGLSPServer implements GLSPServer { +public class DefaultGLSPServer implements GLSPServer { @Inject private ModelStateProvider modelStateProvider; @@ -43,12 +46,32 @@ public class DefaultGLSPServer implements GLSPServer { private ServerStatus status; private GLSPClient clientProxy; + private Class optionsClazz; public DefaultGLSPServer() { + this(null); } + + public DefaultGLSPServer(Class optionsClazz) { + this.optionsClazz = optionsClazz; + } @Override - public void initialize() { + public CompletableFuture initialize(InitializeParameters params) { + try { + if(optionsClazz != null && params.getOptions() instanceof JsonElement) { + T options = new Gson().fromJson((JsonElement)params.getOptions(), optionsClazz); + return handleOptions(options); + } + return handleOptions(null); + } catch(Throwable ex) { + log.error("Could not initialize server due to corrupted options: " + params.getOptions(), ex); + return CompletableFuture.completedFuture(false); + } + } + + protected CompletableFuture handleOptions(T options) { + return CompletableFuture.completedFuture(true); } @Override