@eclipse-emfcloud/modelserver-theia
Version:
## Typescript Client API
195 lines (168 loc) • 7.84 kB
text/typescript
/********************************************************************************
* Copyright (c) 2019-2022 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
* https://www.eclipse.org/legal/epl-2.0, or the MIT License which is
* available at https://opensource.org/licenses/MIT.
*
* SPDX-License-Identifier: EPL-2.0 OR MIT
*******************************************************************************/
import { BackendApplicationContribution } from '@theia/core/lib/node';
import { inject, injectable, optional } from '@theia/core/shared/inversify';
import { ProcessErrorEvent } from '@theia/process/lib/node/process';
import { ProcessManager } from '@theia/process/lib/node/process-manager';
import { RawProcess, RawProcessFactory } from '@theia/process/lib/node/raw-process';
import * as cp from 'child_process';
import { TheiaModelServerClient, TheiaModelServerClientV2 } from '../common';
import { DEFAULT_LAUNCH_OPTIONS, DEFAULT_MODELSERVER_NODE_LAUNCH_OPTIONS, LaunchOptions } from './launch-options';
export const ModelServerLauncher = Symbol('ModelServerLauncher');
export interface ModelServerLauncher {
startServer(): boolean;
dispose(): void;
}
export class DefaultModelServerLauncher implements ModelServerLauncher, BackendApplicationContribution {
protected readonly launchOptions: LaunchOptions = DEFAULT_LAUNCH_OPTIONS;
protected readonly processFactory: RawProcessFactory;
protected readonly processManager: ProcessManager;
protected readonly modelserverClient: TheiaModelServerClient | TheiaModelServerClientV2;
async initialize(): Promise<void> {
try {
const alive = await this.modelserverClient.ping();
if (alive) {
this.logError('Model Server is already running!');
} else {
throw new Error('Could not reach Model Server');
}
} catch (error) {
this.logInfo('Starting Model Server');
this.startServer();
}
}
startServer(): boolean {
if (this.validateLaunch()) {
this.doStartServer();
}
return true;
}
protected doStartServer(): void {
// Note that the existence of the jarPath was previously checked
let args = ['-jar', this.launchOptions.jarPath!, '--port', `${this.resolveJavaServerPort(this.launchOptions)}`];
if (this.launchOptions.vmArgs) {
args = [...this.launchOptions.vmArgs, ...args];
}
if (this.launchOptions.additionalArgs) {
args = [...args, ...this.launchOptions.additionalArgs];
}
this.spawnProcessAsync('java', args);
}
/**
* Resolve the TCP port number that the Java server should be configured to listen on.
*
* @param options the launch options
* @returns the most appropriate port number to configure the Java server process to listen on
*/
protected resolveJavaServerPort(options: LaunchOptions): number {
return options.serverPort ?? DEFAULT_LAUNCH_OPTIONS.serverPort;
}
protected validateLaunch(): boolean {
if (!this.launchOptions.jarPath) {
this.logError('Could not start model server. No path to executable is specified');
return false;
}
return true;
}
protected spawnProcessAsync(command: string, args?: string[], options?: cp.SpawnOptions): Promise<RawProcess> {
const rawProcess = this.processFactory({ command, args, options });
rawProcess.errorStream.on('data', this.logError.bind(this));
rawProcess.outputStream.on('data', this.logInfo.bind(this));
return new Promise<RawProcess>((resolve, reject) => {
rawProcess.onError((error: ProcessErrorEvent) => {
this.onDidFailSpawnProcess(error);
if (error.code === 'ENOENT') {
const guess = command.split(/\s+/).shift();
if (guess) {
reject(new Error(`Failed to spawn ${guess}.\nPerhaps it is not on the PATH.`));
return;
}
}
reject(error);
});
process.nextTick(() => resolve(rawProcess));
});
}
protected onDidFailSpawnProcess(error: Error | ProcessErrorEvent): void {
this.logError(error.message);
}
protected logError(data: string | Buffer): void {
if (data) {
console.error(`ModelServerBackendContribution: ${data.toString().trimEnd()}`);
}
}
protected logInfo(data: string | Buffer): void {
if (data) {
console.info(`ModelServerBackendContribution: ${data.toString().trimEnd()}`);
}
}
dispose(): void {
// nothing to do
}
onStop(): void {
this.dispose();
}
}
export class DefaultModelServerNodeLauncher extends DefaultModelServerLauncher {
protected readonly launchOptions: LaunchOptions = DEFAULT_MODELSERVER_NODE_LAUNCH_OPTIONS;
protected readonly modelserverClient: TheiaModelServerClientV2;
validateLaunch(): boolean {
let result = super.validateLaunch();
if (!this.launchOptions.modelServerNode?.jsPath) {
this.logError('Could not start model server (Node). No path to main script is specified');
result = false;
}
return result;
}
protected doStartServer(): void {
this.startJavaServer();
this.startNodeServer();
}
protected startJavaServer(): void {
// Launch the Java server as per superclass behavior
super.doStartServer();
}
protected resolveJavaServerPort(options: LaunchOptions): number {
return this.resolveUpstreamPort(options);
}
protected startNodeServer(): void {
// Note that validation previously asserted the existence of the `modelServerNode` property
const upstreamPort = this.resolveUpstreamPort(this.launchOptions);
const port = this.resolveNodeServerPort(this.launchOptions);
// Note that the existence of the jsPath was previously checked
let args = [this.launchOptions.modelServerNode!.jsPath!, '--port', `${port}`, '--upstream', `${upstreamPort}`];
if (this.launchOptions.modelServerNode?.additionalArgs) {
args = [...args, ...this.launchOptions.modelServerNode.additionalArgs];
}
this.spawnProcessAsync('node', args);
}
/**
* Resolve the TCP port number that the Node server should be configured to address its upstream Java server.
*
* @param options the launch options
* @returns the most appropriate port number to configure the Node server process to connect to for the upstream Java server
*/
protected resolveUpstreamPort(options: LaunchOptions): number {
// Note that validation previously asserted the existence of the `modelServerNode` property
return options.modelServerNode!.upstreamPort ?? DEFAULT_MODELSERVER_NODE_LAUNCH_OPTIONS.modelServerNode.upstreamPort;
}
/**
* Resolve the TCP port number that the Node server should be configured to listen on.
*
* @param options the launch options
* @returns the most appropriate port number to configure the Node server process to listen on
*/
protected resolveNodeServerPort(options: LaunchOptions): number {
return options.serverPort ?? DEFAULT_MODELSERVER_NODE_LAUNCH_OPTIONS.serverPort;
}
}