Skip to content

Commit

Permalink
Adapt to latest protocol changes
Browse files Browse the repository at this point in the history
# GLSP-1116 Revise model loading
- Refactor `ModelSubmissionHandler` to support proper handling of the `RequestModelAction`
as real request action 
Part-of: eclipse-glsp/glsp#1116

# GLSP-1117: Remove need for explicit definition of client actions

Refactor the base GLSP protocol to allow the client to tell the server which actions it is going to handle i.e. which actions should be forwarded to the client
- Add `clientActions` array to `InitializeClientSessionParams`. This means the client now has to pass the action kinds it wants to handle as part of the initalize request
- Replace `ClientActionHandler` with `ClientActionForwader` a separate component that is not part of the server-side action handlers. 
- Remove `configureClientActions` method from `DiagramModule` as the explicit configuration is no longer needed

Part of eclipse-glsp/glsp/issues/1117

#GLSP-1071: Rename ServerStatus/ServerMessage action
Part of eclipse-glsp/glsp/issues/1071
  • Loading branch information
tortmayr committed Sep 15, 2023
1 parent f0fc2cf commit f5c606c
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 190 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"args": [
"--config",
"${workspaceFolder}/.mocharc",
"${workspaceFolder}/packages/server-node/src/**/*.spec.ts"
"${workspaceFolder}/packages/server/src/**/*.spec.ts"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
Expand Down
2 changes: 2 additions & 0 deletions packages/server/src/common/actions/action-dispatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Logger } from '../utils/logger';
import { DefaultActionDispatcher } from './action-dispatcher';
import { ActionHandler } from './action-handler';
import { ActionHandlerRegistry } from './action-handler-registry';
import { ClientActionForwarder } from './client-action-handler';
import assert = require('assert');

function waitSync(timeInMillis: number): void {
Expand All @@ -48,6 +49,7 @@ describe('test DefaultActionDispatcher', () => {
bind(ClientId).toConstantValue(clientId);
bind(ActionHandlerRegistry).toConstantValue(actionHandlerRegistry);
bind(ClientActionKinds).toConstantValue(['response', 'response1', 'response2']);
bind(ClientActionForwarder).toConstantValue(sinon.createStubInstance(ClientActionForwarder));
})
);
const actionDispatcher = container.resolve(DefaultActionDispatcher);
Expand Down
13 changes: 8 additions & 5 deletions packages/server/src/common/actions/action-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
********************************************************************************/
import { Action, Disposable, flatPush, MaybeArray, RequestAction, ResponseAction, UpdateModelAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ClientActionKinds, ClientId } from '../di/service-identifiers';
import { ClientId } from '../di/service-identifiers';
import { GLSPServerError } from '../utils/glsp-server-error';
import { Logger } from '../utils/logger';
import { PromiseQueue } from '../utils/promise-queue';
import { ActionHandler } from './action-handler';
import { ActionHandlerRegistry } from './action-handler-registry';
import { ClientActionForwarder } from './client-action-handler';

export const ActionDispatcher = Symbol('ActionDispatcher');

Expand Down Expand Up @@ -62,8 +63,8 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable {
@inject(ActionHandlerRegistry)
protected actionHandlerRegistry: ActionHandlerRegistry;

@inject(ClientActionKinds)
protected clientActionKinds: string[];
@inject(ClientActionForwarder)
protected clientActionForwarder: ClientActionForwarder;

@inject(Logger)
private logger: Logger;
Expand All @@ -76,16 +77,18 @@ export class DefaultActionDispatcher implements ActionDispatcher, Disposable {

dispatch(action: Action): Promise<void> {
// Dont queue actions that are just delegated to the client
if (this.clientActionKinds.includes(action.kind)) {
if (this.clientActionForwarder.shouldForwardToClient(action)) {
return this.doDispatch(action);
}
return this.actionQueue.enqueue(() => this.doDispatch(action));
}

protected async doDispatch(action: Action): Promise<void> {
this.logger.debug('Dispatch action:', action.kind);
const handledOnClient = this.clientActionForwarder.handle(action);

const actionHandlers = this.actionHandlerRegistry.get(action.kind);
if (actionHandlers.length === 0) {
if (!handledOnClient && actionHandlers.length === 0) {
throw new GLSPServerError(`No handler registered for action kind: ${action.kind}`);
}

Expand Down
46 changes: 28 additions & 18 deletions packages/server/src/common/actions/client-action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,47 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { Action, ActionMessage, GLSPClientProxy } from '@eclipse-glsp/protocol';
import { inject, injectable, optional } from 'inversify';
import { Action, ActionMessage, GLSPClientProxy, ResponseAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ClientActionKinds, ClientId } from '../di/service-identifiers';
import { ClientAction } from '../protocol/client-action';

/**
* The client action handler is responsible of handling action kinds that are intended for the
* GLSP client, by sending them to the client over json-rpc.
* Component responsible for forwarding actions that are (also) handled by the
* client
*/

@injectable()
export class ClientActionHandler implements ClientActionHandler {
export class ClientActionForwarder {
@inject(GLSPClientProxy)
@optional()
protected glspClient?: GLSPClientProxy;
protected glspClient: GLSPClientProxy;

@inject(ClientId)
protected readonly clientId: string;

constructor(@inject(ClientActionKinds) @optional() public actionKinds: string[] = []) {}
constructor(@inject(ClientActionKinds) public actionKinds: Set<string>) {}

execute(action: Action): [] {
this.send(action);
return [];
/**
* Processes the given action and checks wether it is a
* `clientAction` i.e. an action that should be forwarded to
* the client to be handled there. If the check is successful
* the action is wrapped in an {@link ActionMessage} and sent to the client.
*
* @param action The action to check and forward
* @return `true` if the action was forwarded to the client, `false` otherwise
*/
handle(action: Action): boolean {
if (this.shouldForwardToClient(action)) {
const message: ActionMessage = { action, clientId: this.clientId };
this.glspClient.process(message);
return true;
}
return false;
}

protected send(action: Action): void {
const message: ActionMessage = { action, clientId: this.clientId };
if (this.glspClient) {
this.glspClient.process(message);
return;
shouldForwardToClient(action: Action): boolean {
if (ClientAction.is(action)) {
return false;
}
throw new Error('Could not send message to client. No GLSPClientProxy is defined');
return this.actionKinds.has(action.kind) || ResponseAction.is(action);
}
}
19 changes: 4 additions & 15 deletions packages/server/src/common/actions/global-action-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { expect } from 'chai';
import { Container, ContainerModule } from 'inversify';
import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers';
import * as sinon from 'sinon';
import { DiagramModules, InjectionContainer } from '../di/service-identifiers';
import { ClientSessionInitializer } from '../session/client-session-initializer';
import * as mock from '../test/mock-util';
import { Logger } from '../utils/logger';
import { ActionHandler } from './action-handler';
import { ActionHandlerRegistry } from './action-handler-registry';
import { DefaultGlobalActionProvider } from './global-action-provider';
import * as sinon from 'sinon';
import { expect } from 'chai';

describe('test DefaultGlobalActionProvider', () => {
const container = new Container();
const serverActions = ['A1', 'A2', 'A3'];
const clientActions = ['C1', 'C2'];
const diagramType = 'myDiagramType';

const handler1Actions = ['A1', 'A2'];
Expand All @@ -44,8 +43,6 @@ describe('test DefaultGlobalActionProvider', () => {
bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler1Actions));
bind(ActionHandler).toConstantValue(new mock.StubActionHandler(handler2Actions));
bind(ActionHandlerRegistry).toConstantValue(handlerRegistry);
bind(ClientActionKinds).toConstantValue(clientActions[0]);
bind(ClientActionKinds).toConstantValue(clientActions[1]);
});

const diagramModules = new Map<string, ContainerModule[]>();
Expand All @@ -62,18 +59,10 @@ describe('test DefaultGlobalActionProvider', () => {
const actionProvider = container.resolve(DefaultGlobalActionProvider);

it('serverActionsKinds', () => {
const result = actionProvider.serverActionKinds;
const result = actionProvider.actionKinds;
expect(result.size).to.be.equal(1);
const resultServerActions = result.get(diagramType);
expect(resultServerActions).to.not.be.undefined;
expect(serverActions.every(action => resultServerActions!.includes(action))).true;
});

it('clientActionKinds', () => {
const result = actionProvider.clientActionKinds;
expect(result.size).to.be.equal(1);
const resultClientActions = result.get(diagramType);
expect(resultClientActions).to.not.be.undefined;
expect(clientActions.every(action => resultClientActions!.includes(action))).true;
});
});
46 changes: 21 additions & 25 deletions packages/server/src/common/actions/global-action-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,60 +13,56 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { distinctAdd } from '@eclipse-glsp/protocol';
import { Container, ContainerModule, inject, injectable } from 'inversify';
import { ClientActionKinds, DiagramModules, InjectionContainer } from '../di/service-identifiers';
import { createClientSessionModule } from '../di/client-session-module';
import { DiagramModules, InjectionContainer } from '../di/service-identifiers';
import { ClientSessionInitializer } from '../session/client-session-initializer';
import { ActionHandlerRegistry } from './action-handler-registry';
import { ClientActionHandler } from './client-action-handler';

export const GlobalActionProvider = Symbol('GlobalActionProvider');

/**
* Provides a map of handled action kinds grouped by `diagramType`
*/
export interface GlobalActionProvider {
readonly serverActionKinds: Map<string, string[]>;
readonly clientActionKinds: Map<string, string[]>;
readonly actionKinds: Map<string, string[]>;
}

@injectable()
export class DefaultGlobalActionProvider implements GlobalActionProvider {
public readonly serverActionKinds: Map<string, string[]>;
public readonly clientActionKinds: Map<string, string[]>;
public readonly actionKinds: Map<string, string[]>;

constructor(
@inject(InjectionContainer) serverContainer: Container,
@inject(DiagramModules) diagramModules: Map<string, ContainerModule[]>
) {
this.serverActionKinds = new Map();
this.clientActionKinds = new Map();
this.actionKinds = new Map();
diagramModules.forEach((modules, diagramType) => {
const container = this.createDiagramContainer(serverContainer, modules);
const initializers = container.getAll<ClientSessionInitializer>(ClientSessionInitializer);
initializers.forEach(service => service.initialize());
this.loadServerActionKinds(diagramType, container);
this.loadClientActionKinds(diagramType, container);
this.loadActionKinds(diagramType, container);
container.unbindAll();
});
}

createDiagramContainer(serverContainer: Container, modules: ContainerModule[]): Container {
const container = serverContainer.createChild();
container.load(...modules);
const clientSessionModule = createClientSessionModule({
clientId: 'tempId',
// eslint-disable-next-line @typescript-eslint/no-empty-function
glspClient: { process: () => {} },
clientActionKinds: []
});
container.load(...modules, clientSessionModule);
return container;
}

loadServerActionKinds(diagramType: string, diagramContainer: Container): void {
loadActionKinds(diagramType: string, diagramContainer: Container): void {
const handlerRegistry = diagramContainer.get<ActionHandlerRegistry>(ActionHandlerRegistry);
const diagramServerActions = this.serverActionKinds.get(diagramType) ?? [];
handlerRegistry
.getAll()
.filter(handler => !(handler instanceof ClientActionHandler))
.forEach(handler => diagramServerActions.push(...handler.actionKinds));
this.serverActionKinds.set(diagramType, [...new Set(diagramServerActions)]);
}

loadClientActionKinds(diagramType: string, diagramContainer: Container): void {
const clientActionKinds = diagramContainer.getAll<string>(ClientActionKinds);
const diagramClientActions = this.clientActionKinds.get(diagramType) ?? [];
diagramClientActions.push(...clientActionKinds);
this.clientActionKinds.set(diagramType, diagramClientActions);
const diagramServerActions = this.actionKinds.get(diagramType) ?? [];
handlerRegistry.getAll().forEach(handler => distinctAdd(diagramServerActions, ...handler.actionKinds));
this.actionKinds.set(diagramType, diagramServerActions);
}
}
27 changes: 17 additions & 10 deletions packages/server/src/common/di/client-session-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { GLSPClientProxy } from '@eclipse-glsp/protocol';
import { GLSPClientProxy, bindOrRebind } from '@eclipse-glsp/protocol';
import { ContainerModule } from 'inversify';
import { ClientId } from './service-identifiers';
import { ClientActionKinds, ClientId } from './service-identifiers';

export function createClientSessionModule(clientId: string, glspClient: GLSPClientProxy): ContainerModule {
return new ContainerModule((bind, _unbind, isBound, rebind) => {
if (isBound(ClientId)) {
rebind(ClientId).toConstantValue(clientId);
} else {
bind(ClientId).toConstantValue(clientId);
}
bind(GLSPClientProxy).toConstantValue(glspClient);
export interface ClientSessionModuleOptions {
clientId: string;
glspClient: GLSPClientProxy;
clientActionKinds: string[];
}

/**
* Creates the DI module that binds client session specific configuration
*/
export function createClientSessionModule(options: ClientSessionModuleOptions): ContainerModule {
return new ContainerModule((bind, unbind, isBound, rebind) => {
const context = { bind, unbind, isBound, rebind };
bindOrRebind(context, ClientId).toConstantValue(options.clientId);
bind(GLSPClientProxy).toConstantValue(options.glspClient);
bind(ClientActionKinds).toConstantValue(new Set(options.clientActionKinds));
});
}
Loading

0 comments on commit f5c606c

Please sign in to comment.