UNPKG

@eclipse-emfcloud/modelserver-theia

Version:
195 lines (168 loc) 7.84 kB
/******************************************************************************** * 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; } @injectable() export class DefaultModelServerLauncher implements ModelServerLauncher, BackendApplicationContribution { @inject(LaunchOptions) @optional() protected readonly launchOptions: LaunchOptions = DEFAULT_LAUNCH_OPTIONS; @inject(RawProcessFactory) protected readonly processFactory: RawProcessFactory; @inject(ProcessManager) protected readonly processManager: ProcessManager; @inject(TheiaModelServerClient) 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(); } } @injectable() export class DefaultModelServerNodeLauncher extends DefaultModelServerLauncher { @inject(LaunchOptions) @optional() protected readonly launchOptions: LaunchOptions = DEFAULT_MODELSERVER_NODE_LAUNCH_OPTIONS; @inject(TheiaModelServerClientV2) 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; } }