Skip to content

Commit

Permalink
FUSETOOLS2-826 - add dependencies in classpath
Browse files Browse the repository at this point in the history
provide command to add dependencies on classpath.

no test to handle 1.3- as this use case is considered as best effort
only.

for next iterations:
- automatically call refresh when needed (to be determined when it is
needed)
- use a specific folder per file
- find a way to support two files opened at same time in different
window which are not sharing the same dependencies
- allow to configure parameters to provide to the call (like a maven
repository)
- provide progress report of refresh

limitations due to upstream:
- some notations not supported
apache/camel-k#2213
- a lot of place is taken in internal folders as jars are duplicated
instead of simply pointing to local .m2
apache/camel-k#2179
- use an API to refresh the classpath to avoid workaround of changing
back and forth the settings
redhat-developer/vscode-java#1874

Signed-off-by: Aurélien Pupier <apupier@redhat.com>
  • Loading branch information
apupier committed Apr 28, 2021
1 parent 62d683c commit 47f91db
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 42 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to the "vscode-camelk" extension will be documented in this
## 0.0.24

- Update default runtime version to v1.4.0
- Provide command to refresh Java classpath. It allows to have dependencies declared as modeline part of classpath.
- It requires Camel K 1.4.0
- If mistakenly called with Camel K 1.3.2-, need to restart VS Code for basic dependencies to be available again.
- It supports modeline dependencies notation from local build. See [apache/camel-k#2213](https://github.com/apache/camel-k/issues/2213)
- A single classpath is provided. It means that refresh command needs to be called when switching between Integration file written in Java that does not have the same dependencies.
- There is no progress indicator. Please be patient, on slow network, the first time it can take several minutes.

## 0.0.23

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ Note: By default, `Auto-scroll` is checked and new entries in the log will autom
1. From the **Apache Camel K Integrations** view, right-click the integration that you want to stop.
2. Select **Remove Apache Camel K Integration**.

## Java language support

To benefit from Java Language completion on standalone files, [VS Code Language support for java](https://marketplace.visualstudio.com/items?itemName=redhat.java) needs to be installed. An invisble project is created with a default classpath.

The command `Refresh local Java classpath for Camel K standalone file based on current editor. Available with kamel 1.4+.` allows to have specfic dependencies of the opened Integration part of the classpath. For isntance, specific dependencies can be decalred as modeline.

Be aware of the following limitations:
- It requires Camel K 1.4.0
- If mistakenly called with Camel K 1.3.2-, need to restart VS Code for basic dependencies to be available again.
- It supports modeline dependencies notation from local build. See [apache/camel-k#2213](https://github.com/apache/camel-k/issues/2213)
- A single classpath is provided. It means that refresh command needs to be called when switching between Integration file written in Java that does not have the same dependencies.
- There is no progress indicator. Please be patient, on slow network, the first time it can take several minutes.

## Apache Camel K Extension Settings

To access **Tooling for Apache Camel K** extension settings:
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
"command": "camelk.integrations.createNewIntegrationFile",
"title": "Create a new Apache Camel K Integration file",
"enablement": "workspaceFolderCount != 0"
},
{
"command": "camelk.classpath.refresh",
"title": "Refresh local Java classpath for Camel K standalone file based on current editor. Available with kamel 1.4+.",
"enablement": "editorIsOpen && editorLangId == java"
}
],
"menus": {
Expand Down
12 changes: 6 additions & 6 deletions resources/maven-project/pom-to-copy-java-dependencies.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<version>0.0.1</version>
<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core-engine</artifactId>
<version>${camelVersion}</version>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
</dependency>
</dependencies>
<build>
Expand All @@ -24,9 +24,9 @@
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core-engine</artifactId>
<version>${camelVersion}</version>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<outputDirectory>${project.build.directory}</outputDirectory>
</artifactItem>
</artifactItems>
Expand Down
124 changes: 97 additions & 27 deletions src/JavaDependenciesManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,37 @@
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as kamelCli from './kamel';
import * as utils from './CamelKJSONUtils';

const PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES = "java.project.referencedLibraries";
export const CAMEL_VERSION = "3.9.0";

export async function initializeJavaDependenciesManager(context: vscode.ExtensionContext): Promise<void> {
const pomTemplate = context.asAbsolutePath(path.join('resources', 'maven-project', 'pom-to-copy-java-dependencies.xml'));
const destination = destinationFolderForDependencies(context);
await downloadDependencies(destination, pomTemplate);
await initializeJavaSettingManagement(destination);
const destination = parentDestinationFolderForDependencies(context);
await downloadCommonCamelKJavaDependencies(context);
await initializeJavaSettingManagement(path.join(destination, 'dependencies'));
}

async function downloadDependencies(destination: string, pomTemplate: string) {
fs.mkdirSync(destination, { recursive: true });
async function downloadJavaDependencies(context: vscode.ExtensionContext, groupId: string, artifactId: string, version: string) {
const destination = parentDestinationFolderForDependencies(context);
const pomTemplate = context.asAbsolutePath(path.join('resources', 'maven-project', 'pom-to-copy-java-dependencies.xml'));
const directDependenciesDestination = path.join(destination, 'dependencies');
fs.mkdirSync(directDependenciesDestination, { recursive: true });

/* provides only camel-core-engine dependencies for now, to improve:
* - rely on kamel inspect to know all extra potential libraries that can be provided
*/
const mvn = require('maven').create({
cwd: destination,
cwd: directDependenciesDestination,
file: pomTemplate
});
await mvn.execute(['dependency:copy-dependencies'], { 'camelVersion': CAMEL_VERSION, 'outputDirectory': destination });

mvn.execute(['dependency:copy-dependencies'],
{
'groupId': groupId,
'artifactId': artifactId,
'version': version,
'outputDirectory': directDependenciesDestination
});
return directDependenciesDestination;
}

async function initializeJavaSettingManagement(destination: string) {
Expand All @@ -51,26 +60,84 @@ async function initializeJavaSettingManagement(destination: string) {
}
}

export function destinationFolderForDependencies(context: vscode.ExtensionContext) {
let extensionStorage = context.globalStoragePath;
export async function downloadCommonCamelKJavaDependencies(context: vscode.ExtensionContext): Promise<string> {
const groupId = 'org.apache.camel';
const artifactId = 'camel-core-engine';
return downloadJavaDependencies(context, groupId, artifactId, CAMEL_VERSION);
}

export async function downloadSpecificCamelKJavaDependencies(
context: vscode.ExtensionContext,
uri: vscode.Uri | undefined,
mainOutputChannel: vscode.OutputChannel): Promise<void>{
if(uri === undefined) {
uri = vscode.window.activeTextEditor?.document.uri;
}
if (uri) {
const kamelLocal = kamelCli.create();
const parentDestination = parentDestinationFolderForDependencies(context);
await clearDestinationFolder(mainOutputChannel, parentDestination);
const command = `local build --integration-directory "${parentDestination}" "${uri.path}"`;
try {
await kamelLocal.invoke(command);
triggerRefreshOfJavaClasspath(context);
} catch(error) {
if(error instanceof Error) {
if(error.message.includes('unknown flag: --maven-repository')) {
utils.shareMessage(mainOutputChannel, 'A newer version of kamel CLI must be used to refresh classpath. 1.4+ is required. Either use a newer version and called again the refresh classpath action or restart VS Code to get back to basic dependencies available in classpath.');
return;
}
}
utils.shareMessage(mainOutputChannel, `Error while trying to refresh Java classpath based on file ${uri.path}:\n${error}`);
}
} else {
utils.shareMessage(mainOutputChannel, 'Cannot determine which file to use as base to refresh classpath');
}
}

async function clearDestinationFolder(outputChannel: vscode.OutputChannel, parentDestination: string) {
try {
await vscode.workspace.fs.delete(vscode.Uri.file(parentDestination), { recursive: true });
} catch(error) {
utils.shareMessage(outputChannel, `Cannot clear folder ${parentDestination}\n: ${error}`);
}
}

function triggerRefreshOfJavaClasspath(context: vscode.ExtensionContext) {
const destination = path.join(destinationFolderForDependencies(context));
/**
* This is currently a workaround as no API is available to trigger the refresh of the classpath.
* To trigger the refresh, we modify back and forth the settings. It is refreshing twice but the best that we can do for now.
* See https://github.com/redhat-developer/vscode-java/issues/1874
**/
updateReferencedLibraries("", destination);
updateReferenceLibraries(vscode.window.activeTextEditor, destination);
}

export function parentDestinationFolderForDependencies(context: vscode.ExtensionContext) {
const extensionStorage = context.globalStoragePath;
return path.join(extensionStorage, `java-dependencies-${CAMEL_VERSION}`);
}

export function updateReferenceLibraries(editor: vscode.TextEditor | undefined, destination:string) {
const camelKReferencedLibrariesPattern = destination + '/*.jar';
let documentEdited = editor?.document;
if (documentEdited?.fileName.endsWith(".java")) {
let text = documentEdited.getText();
const configuration = vscode.workspace.getConfiguration();
let refLibrariesTopLevelConfig = configuration.get(PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES);
if(refLibrariesTopLevelConfig instanceof Array) {
updateReferenceLibrariesForConfigKey(text, refLibrariesTopLevelConfig, camelKReferencedLibrariesPattern, configuration, PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES);
} else {
let includepropertyKeyConfig = PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES + '.include';
let refLibrariesIncludeConfig = configuration.get(includepropertyKeyConfig) as Array<string>;
updateReferenceLibrariesForConfigKey(text, refLibrariesIncludeConfig, camelKReferencedLibrariesPattern, configuration, includepropertyKeyConfig);
}
}
const documentEdited = editor?.document;
if (documentEdited?.fileName.endsWith(".java")) {
const text = documentEdited.getText();
updateReferencedLibraries(text, destination);
}
}

function updateReferencedLibraries(text: string, destination: string) {
const camelKReferencedLibrariesPattern = destination + '/*.jar';
const configuration = vscode.workspace.getConfiguration();
const refLibrariesTopLevelConfig = configuration.get(PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES);
if (refLibrariesTopLevelConfig instanceof Array) {
updateReferenceLibrariesForConfigKey(text, refLibrariesTopLevelConfig, camelKReferencedLibrariesPattern, configuration, PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES);
} else {
const includepropertyKeyConfig = PREFERENCE_KEY_JAVA_REFERENCED_LIBRARIES + '.include';
const refLibrariesIncludeConfig = configuration.get(includepropertyKeyConfig) as Array<string>;
updateReferenceLibrariesForConfigKey(text, refLibrariesIncludeConfig, camelKReferencedLibrariesPattern, configuration, includepropertyKeyConfig);
}
}

function updateReferenceLibrariesForConfigKey(text: string, refLibrariesConfig: string[], camelKReferencedLibrariesPattern: string, configuration: vscode.WorkspaceConfiguration, configurationKey: string) {
Expand All @@ -96,3 +163,6 @@ function ensureReferencedLibrariesContainsCamelK(refLibrariesConfig: string[], c
configuration.update(configurationKey, refLibrariesConfig);
}
}
export function destinationFolderForDependencies(context: vscode.ExtensionContext) {
return path.join(parentDestinationFolderForDependencies(context), 'dependencies');
}
3 changes: 2 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import * as kubectl from './kubectl';
import * as kamel from './kamel';
import * as kubectlutils from './kubectlutils';
import * as config from './config';
import { initializeJavaDependenciesManager } from './JavaDependenciesManager';
import { downloadSpecificCamelKJavaDependencies, initializeJavaDependenciesManager } from './JavaDependenciesManager';
import { CamelKTaskCompletionItemProvider } from './task/CamelKTaskCompletionItemProvider';
import { CamelKTaskProvider } from './task/CamelKTaskDefinition';
import { ChildProcess } from 'child_process';
Expand Down Expand Up @@ -175,6 +175,7 @@ export async function activate(context: vscode.ExtensionContext) {

vscode.commands.registerCommand('camelk.integrations.createNewIntegrationFile', async (...args:any[]) => { await NewIntegrationFileCommand.create(args);});
vscode.commands.registerCommand('camelk.integrations.selectFirstNode', () => { selectFirstItemInTree();});
vscode.commands.registerCommand('camelk.classpath.refresh', async (uri:vscode.Uri) => { await downloadSpecificCamelKJavaDependencies(context, uri, mainOutputChannel)});
});

initializeJavaDependenciesManager(context);
Expand Down
25 changes: 17 additions & 8 deletions src/test/suite/_completion/completion.java.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,27 @@ const TOTAL_TIMEOUT = DOWNLOAD_JAVA_DEPENDENCIES_TIMEOUT + JAVA_EXTENSION_READIN

// TODO: skipped on jenkins due to FUSETOOLS2-578
suite('Should do completion in Camel K standalone files', () => {
const docUriJava = getDocUri('MyRouteBuilder.java');

const expectedCompletion = { label: 'from(String uri) : RouteDefinition'};


var testVar = test('Completes from method for Java', async () => {
const docUriJava = getDocUri('MyRouteBuilder.java');
const expectedCompletion = { label: 'from(String uri) : RouteDefinition'};
Utils.skipOnJenkins(testVar);
await testCompletion(docUriJava, new vscode.Position(5, 11), expectedCompletion);
await testCompletion(docUriJava, new vscode.Position(5, 11), expectedCompletion, false);
}).timeout(TOTAL_TIMEOUT);

var testAdditionalDependencies = test('Completes additional dependencies', async () => {
const docUriJava = getDocUri('MyRouteBuilderWithAdditionalDependencies.java');
const expectedCompletion = { label: 'ArithmeticUtils - org.apache.commons.math3.util'};
Utils.skipOnJenkins(testAdditionalDependencies);
await testCompletion(docUriJava, new vscode.Position(6, 19), expectedCompletion, true);
}).timeout(TOTAL_TIMEOUT);
});

async function testCompletion(
docUri: vscode.Uri,
position: vscode.Position,
expectedCompletion: vscode.CompletionItem
expectedCompletion: vscode.CompletionItem,
refreshClasspath: boolean
) {
await waitUntil(()=> {
const destination = retrieveDestination();
Expand All @@ -61,6 +68,9 @@ async function testCompletion(

let doc = await vscode.workspace.openTextDocument(docUri);
await vscode.window.showTextDocument(doc);
if(refreshClasspath) {
await vscode.commands.executeCommand('camelk.classpath.refresh', docUri);
}
let javaExtension: vscode.Extension<any> | undefined;
await waitUntil(() => {
javaExtension = vscode.extensions.getExtension('redhat.java');
Expand All @@ -86,7 +96,6 @@ async function testCompletion(

function retrieveDestination() {
const context = Utils.retrieveExtensionContext();
const destination = JavaDependenciesManager.destinationFolderForDependencies(context);
return destination;
return JavaDependenciesManager.destinationFolderForDependencies(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// camel-k: dependency=mvn:org.apache.commons:commons-math3:3.6.1

import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilderWithAdditionalDependencies extends RouteBuilder{
public void configure() {
ArithmeticUt
}
}

0 comments on commit 47f91db

Please sign in to comment.