Skip to content

Merge 24.1.0 changes to main #443

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,20 @@ If an action has been applied to the Maven `pom.xml` file through the extension,
1. Check the `pom.xml` file for any duplicated tags.
2. If duplicated tags are found, remove the extra tags and attempt to compile again.

## Telemetry

You may choose to enable the Oracle Java extension for Visual Studio Code (\"*JVSCE*\") to collect and send anonymous technical data commonly known as \"*telemetry data*\" to Oracle to help improve the Java platform.
- No personal information nor source code is collected.
- You may refer to the data collection and privacy policy for JVSCE at [TELEMETRY.md](vscode/TELEMETRY.md).
- No information is sent prior to you enabling Telemetry.

### Enabling/Disabling Telemetry
If you wish to enable or disable the collection and transmission of the telemetry data, you may do so in the following ways.
1. Notification pop-up request to enable.
- Appears at the time of activation of the extension, when you have not made a choice for this setting.
2. The Java extension setting: [`jdk.telemetry.enabled`](vscode://settings/jdk.telemetry.enabled)
3. *(On Microsoft Visual Studio Code)* The global VS Code setting: [`telemetry.telemetryLevel`](vscode://settings/telemetry.telemetryLevel) must be set to `all` for enabling JVSCE telemetry.

## Contributing

This project welcomes contributions from the community. Before submitting a pull request, please [review our contribution guide](./CONTRIBUTING.md)
Expand Down
14 changes: 11 additions & 3 deletions vscode/src/telemetry/events/baseEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
import { LOGGER } from "../../logger";
import { AnonymousIdManager } from "../impl/AnonymousIdManager";
import { cacheService } from "../impl/cacheServiceImpl";
import { getHashCode } from "../utils";
import { cacheServiceIndex } from "../impl/cache";
import { getHashCode, getValuesToBeTransformed, transformValue } from "../utils";

export interface BaseEventPayload {
vsCodeId: string;
Expand All @@ -26,6 +26,7 @@ export interface BaseEventPayload {
export abstract class BaseEvent<T> {
protected _payload: T & BaseEventPayload;
protected _data: T
private static readonly blockedValues = getValuesToBeTransformed();

constructor(public readonly NAME: string,
public readonly ENDPOINT: string,
Expand All @@ -47,6 +48,13 @@ export abstract class BaseEvent<T> {
return this._data;
}

protected static transformEvent = (propertiesToTransform: string[], payload: Record<string, any>): any => {
const replacedValue = "_REM_";

return transformValue(null, this.blockedValues, propertiesToTransform, replacedValue, payload);
};


public onSuccessPostEventCallback = async (): Promise<void> => {
LOGGER.debug(`${this.NAME} sent successfully`);
}
Expand All @@ -58,7 +66,7 @@ export abstract class BaseEvent<T> {
protected addEventToCache = (): void => {
const dataString = JSON.stringify(this.getData);
const calculatedHashVal = getHashCode(dataString);
cacheService.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
cacheServiceIndex.simpleCache.put(this.NAME, calculatedHashVal).then((isAdded: boolean) => {
LOGGER.debug(`${this.NAME} added in cache ${isAdded ? "Successfully" : "Unsucessfully"}`);
});
}
Expand Down
4 changes: 3 additions & 1 deletion vscode/src/telemetry/events/jdkFeature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ export interface JdkFeatureEventData {
export class JdkFeatureEvent extends BaseEvent<JdkFeatureEventData> {
public static readonly NAME = "jdkFeature";
public static readonly ENDPOINT = "/jdkFeature";
private static readonly propertiesToTransform = ['javaVersion'];

constructor(payload: JdkFeatureEventData) {
super(JdkFeatureEvent.NAME, JdkFeatureEvent.ENDPOINT, payload);
const updatedPayload: JdkFeatureEventData = BaseEvent.transformEvent(JdkFeatureEvent.propertiesToTransform, payload);
super(JdkFeatureEvent.NAME, JdkFeatureEvent.ENDPOINT, updatedPayload);
}

public static concatEvents(events:JdkFeatureEvent[]): JdkFeatureEvent[] {
Expand Down
8 changes: 5 additions & 3 deletions vscode/src/telemetry/events/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
import { globalState } from "../../globalState";
import { LOGGER } from "../../logger";
import { cacheService } from "../impl/cacheServiceImpl";
import { cacheServiceIndex } from "../impl/cache";
import { getEnvironmentInfo } from "../impl/enviromentDetails";
import { getHashCode } from "../utils";
import { BaseEvent } from "./baseEvent";
Expand Down Expand Up @@ -53,9 +53,11 @@ export interface StartEventData {
export class ExtensionStartEvent extends BaseEvent<StartEventData> {
public static readonly NAME = "startup";
public static readonly ENDPOINT = "/start";
private static readonly propertiesToTransform = ['osVersion'];

constructor(payload: StartEventData) {
super(ExtensionStartEvent.NAME, ExtensionStartEvent.ENDPOINT, payload);
const updatedPayload: StartEventData = BaseEvent.transformEvent(ExtensionStartEvent.propertiesToTransform, payload);
super(ExtensionStartEvent.NAME, ExtensionStartEvent.ENDPOINT, updatedPayload);
}

onSuccessPostEventCallback = async (): Promise<void> => {
Expand All @@ -65,7 +67,7 @@ export class ExtensionStartEvent extends BaseEvent<StartEventData> {

public static builder = (): ExtensionStartEvent | null => {
const startEventData = getEnvironmentInfo(globalState.getExtensionContextInfo());
const cachedValue: string | undefined = cacheService.get(this.NAME);
const cachedValue: string | undefined = cacheServiceIndex.simpleCache.get(this.NAME);
const envString = JSON.stringify(startEventData);
const newValue = getHashCode(envString);

Expand Down
28 changes: 27 additions & 1 deletion vscode/src/telemetry/events/workspaceChange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { randomUUID } from "crypto";
import { LOGGER } from "../../logger";
import { Telemetry } from "../telemetry";
import { BaseEvent } from "./baseEvent";
import { cacheServiceIndex } from "../impl/cache";
import { ProjectCacheValue } from "../impl/cache/projectCacheValue";

interface ProjectInfo {
id: string;
Expand All @@ -37,9 +40,32 @@ let workspaceChangeEventTimeout: NodeJS.Timeout | null = null;
export class WorkspaceChangeEvent extends BaseEvent<WorkspaceChangeData> {
public static readonly NAME = "workspaceChange";
public static readonly ENDPOINT = "/workspaceChange";
private static readonly propertiesToTransform = ['javaVersion'];

constructor(payload: WorkspaceChangeData) {
super(WorkspaceChangeEvent.NAME, WorkspaceChangeEvent.ENDPOINT, payload);
const updatedPayload: WorkspaceChangeData = WorkspaceChangeEvent.transformPayload(payload);
super(WorkspaceChangeEvent.NAME, WorkspaceChangeEvent.ENDPOINT, updatedPayload);
}

private static transformPayload = (payload: WorkspaceChangeData) => {
const transformedPayload: WorkspaceChangeData = BaseEvent.transformEvent(WorkspaceChangeEvent.propertiesToTransform, payload);
return WorkspaceChangeEvent.updateProjectId(transformedPayload)
}

private static updateProjectId = (payload: WorkspaceChangeData) => {
const updatedProjectInfo = payload.projectInfo.map(project => {
const existingId = cacheServiceIndex.projectCache.get(project.id);
const uniqueId = existingId ?? randomUUID();

if (!existingId) {
// Cannot be awaited because the caller is constructor and it cannot be a async call
cacheServiceIndex.projectCache.put(project.id, new ProjectCacheValue(uniqueId));
}

return { ...project, id: uniqueId };
});

return { ...payload, projectInfo: updatedProjectInfo };
}

public onSuccessPostEventCallback = async (): Promise<void> => {
Expand Down
29 changes: 29 additions & 0 deletions vscode/src/telemetry/impl/cache/BaseCacheValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright (c) 2025, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export type CacheValueObj<T> = {
type: string;
payload: T;
lastUsed: number;
}

export abstract class BaseCacheValue<T> {
public readonly lastUsed: number;

constructor(public readonly type: string, public readonly payload: T, lastUsed?: number) {
this.lastUsed = lastUsed ?? Date.now();
}
}
23 changes: 23 additions & 0 deletions vscode/src/telemetry/impl/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
Copyright (c) 2025, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { ProjectCacheService } from "./projectCacheService";
import { SimpleCacheService } from "./simpleCacheService";

export namespace cacheServiceIndex {
export const simpleCache = new SimpleCacheService();
export const projectCache = new ProjectCacheService();
}
83 changes: 83 additions & 0 deletions vscode/src/telemetry/impl/cache/projectCacheService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright (c) 2024-2025, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CacheService } from "../../types";
import { LOGGER } from "../../../logger";
import { globalState } from "../../../globalState";
import { isError } from "../../../utils";
import { removeEntriesOnOverflow } from "./utils";
import { ProjectCacheValue } from "./projectCacheValue";

export class ProjectCacheService implements CacheService<ProjectCacheValue, String> {
readonly MAX_KEYS_SIZE: number = 5000;
private removingKeys: boolean = false;

public get = (key: string) => {
try {
const updatedKey = this.getUpdatedKey(key);
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();

const value = vscGlobalState.get<ProjectCacheValue>(updatedKey);
if (value) {
this.put(updatedKey, ProjectCacheValue.fromObject({ ...value, lastUsed: Date.now() }));
}

return value?.payload;
} catch (err) {
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
return undefined;
}
}

public put = async (key: string, value: ProjectCacheValue) => {
try {
const updatedKey = this.getUpdatedKey(key);
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();

await vscGlobalState.update(updatedKey, value);
if (vscGlobalState.keys().length > this.MAX_KEYS_SIZE) {
this.removeOnOverflow();
}
LOGGER.debug(`Updating key: ${key} to ${value}`);

return true;
} catch (err) {
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
return false;
}
}

public removeOnOverflow = async () => {
try {
if (this.removingKeys) {
LOGGER.log("Ignoring removing keys request, since it is already in progress");
return;
}
this.removingKeys = true;

const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
const comparator = (a: ProjectCacheValue, b: ProjectCacheValue) => (a.lastUsed - b.lastUsed);

await removeEntriesOnOverflow(vscGlobalState, ProjectCacheValue.type, comparator);
} catch (error) {
LOGGER.error("Some error occurred while removing keys " + (isError(error) ? error.message : error));
} finally {
this.removingKeys = false;
}
}

// for unit tests needs to be public
public getUpdatedKey = (key: string) => `${ProjectCacheValue.type}.${key}`;
}
34 changes: 34 additions & 0 deletions vscode/src/telemetry/impl/cache/projectCacheValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
Copyright (c) 2025, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { BaseCacheValue, CacheValueObj } from "./BaseCacheValue";

export class ProjectCacheValue extends BaseCacheValue<string> {
public static readonly type = "prjId";

constructor(payload: string, lastUsed?: number){
super(ProjectCacheValue.type, payload, lastUsed);
}

public static fromObject(obj: CacheValueObj<string>): ProjectCacheValue {
if (obj.type !== ProjectCacheValue.type) {
throw new Error(`Invalid object type for ProjectCacheEntry: received ${obj.type}`);
}
const entry = new ProjectCacheValue(obj.payload, obj.lastUsed);

return entry;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2024-2025, Oracle and/or its affiliates.
Copyright (c) 2025, Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -13,32 +13,32 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { CacheService } from "../types";
import { LOGGER } from "../../logger";
import { globalState } from "../../globalState";

class CacheServiceImpl implements CacheService {
public get = (key: string): string | undefined => {
import { globalState } from "../../../globalState";
import { LOGGER } from "../../../logger";
import { CacheService } from "../../types";

export class SimpleCacheService implements CacheService<string, string> {
get(key: string) {
try {
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
return vscGlobalState.get(key);
return vscGlobalState.get<string>(key);
} catch (err) {
LOGGER.error(`Error while retrieving ${key} from cache: ${(err as Error).message}`);
return undefined;
}
}

public put = async (key: string, value: string): Promise<boolean> => {
async put(key: string, value: string) {
try {
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
await vscGlobalState.update(key, value);
LOGGER.debug(`Updating key: ${key} to ${value}`);

return true;
} catch (err) {
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);
return false;
}
}
}

export const cacheService = new CacheServiceImpl();
Loading