Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3ba3b8e
Specify version 24.0.0.2 for binary scanner
turkeylurkey Apr 8, 2025
5646c12
Merge pull request #474 from turkeylurkey/issue-1874a
turkeylurkey May 16, 2025
8159bbd
Add string for temp dir for generate features
turkeylurkey Jun 11, 2025
8a9d6c5
Add generateToSrc option to dev mode
turkeylurkey May 28, 2025
b04e985
Toggle generateToSrc option using hotkey
turkeylurkey May 29, 2025
495a118
Update src/main/java/io/openliberty/tools/common/plugins/util/DevUtil…
turkeylurkey Nov 7, 2025
5877cc8
Merge pull request #476 from turkeylurkey/issue-1892
turkeylurkey Nov 10, 2025
ba2777d
Handle the generateToSrc option in devmode
turkeylurkey Jun 16, 2025
fea5c1c
Update test case
turkeylurkey Jun 16, 2025
b94075a
Display the correct info message when server file is in a temp dir
turkeylurkey Jun 16, 2025
c89b196
Better comments
turkeylurkey Jun 16, 2025
a14c7ec
Add constant for generated features overrides directory
turkeylurkey Jun 16, 2025
b0a2ab8
Add support for feature gen to server dir for class file change
turkeylurkey Jun 16, 2025
c73102d
Clean up method copyTempFeatureFileToServer
turkeylurkey Jun 16, 2025
a6f7106
Keep temp generate features file in configDropins dir even in temp dir
turkeylurkey Jun 17, 2025
52fe283
generatFeaturesFile is either configDir or tempDir
turkeylurkey Jun 17, 2025
49cbf35
When you press hotkey o optionally generate to temp dir
turkeylurkey Jun 17, 2025
4c0052a
Register the generated features temp dir with the file watcher
turkeylurkey Jun 18, 2025
b8679df
Remove code added to process the generated features file in favour of…
turkeylurkey Jun 19, 2025
bc93cb1
Use generateToSrc option in generate features after config file deletion
turkeylurkey Jun 23, 2025
3ae5530
Register gen. features file to watch in dev mode
turkeylurkey Jun 25, 2025
67d7b34
Update copyright
turkeylurkey Dec 4, 2025
980d066
Clarify a comment
turkeylurkey Dec 5, 2025
502d3db
Rename a method to make more sense
turkeylurkey Dec 5, 2025
1c6fb16
Rename a field
turkeylurkey Dec 11, 2025
16d3050
Merge pull request #479 from turkeylurkey/issue-1897a
turkeylurkey Dec 15, 2025
a42ffc8
Rename generate features fields
turkeylurkey Dec 17, 2025
d384f65
Merge pull request #493 from turkeylurkey/issue-1952
turkeylurkey Dec 18, 2025
5905974
Copy config and changed file to temp dir for feature generation
turkeylurkey Jan 21, 2026
2fd65f3
Code review updates: remove unneccessary directory and update comments
turkeylurkey Feb 3, 2026
1de62ee
Merge pull request #494 from turkeylurkey/issue-1959
turkeylurkey Feb 3, 2026
877e147
Pass the generateToSrc option when generating features
turkeylurkey Jan 28, 2026
2e59317
Code review: rename variable and update comment
turkeylurkey Feb 4, 2026
5c0f9b2
Merge pull request #495 from turkeylurkey/issue-1963
turkeylurkey Feb 5, 2026
f1261b5
Fix merge conflicts
turkeylurkey Feb 6, 2026
22403f6
Merge pull request #497 from turkeylurkey/merge-main-0130
turkeylurkey Feb 6, 2026
1eb9ff4
Make optimizeGenerateFeatures() protected
turkeylurkey Feb 10, 2026
7b2bf2f
Remove unused method getJavaSourceClassPaths()
turkeylurkey Feb 11, 2026
79a1d16
Track when generate-features file is copied early to prevent copying …
turkeylurkey Feb 18, 2026
6167b7e
Merge pull request #500 from turkeylurkey/issue-1967
turkeylurkey Feb 20, 2026
bde6685
Add coordinates for feature files
turkeylurkey Apr 2, 2026
e8eba65
Add feature list file parameters to binary scanner
turkeylurkey Apr 2, 2026
7f137d0
Update the type of file to find for liberty base feature list
turkeylurkey Apr 2, 2026
515e3a8
Reduce the duplication in the feature list coordinates
turkeylurkey Apr 6, 2026
e43e66e
Remove debug statement
turkeylurkey Apr 6, 2026
4a34e59
Rework: clarify the name of open liberty Maven coordinates
turkeylurkey Apr 17, 2026
0d1c691
Merge pull request #506 from turkeylurkey/issue-1981
turkeylurkey Apr 17, 2026
8f717ff
Updated user facing output
TrevCraw Apr 20, 2026
ab17bc3
Use the binary scanner API with a Map
turkeylurkey Apr 20, 2026
7d5552a
Merge pull request #508 from TrevCraw/message-updates
TrevCraw Apr 21, 2026
a3d7d38
Rework, rename variable
turkeylurkey Apr 21, 2026
3aa3cb8
Rework, rename variable no. 2
turkeylurkey Apr 21, 2026
a1230dd
Merge pull request #509 from turkeylurkey/issue-2001
turkeylurkey Apr 21, 2026
72e02b9
Merge branch 'main' into generate-features-update
turkeylurkey Apr 21, 2026
fc4b382
Merge pull request #510 from turkeylurkey/merge-main-0421
turkeylurkey Apr 21, 2026
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* (C) Copyright IBM Corporation 2021, 2025.
* (C) Copyright IBM Corporation 2021, 2026
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,17 +23,50 @@
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class BinaryScannerUtil {

public static final String BINARY_SCANNER_MAVEN_GROUP_ID = "com.ibm.websphere.appmod.tools";
public static final String BINARY_SCANNER_MAVEN_ARTIFACT_ID = "binary-app-scanner";
public static final String BINARY_SCANNER_MAVEN_TYPE = "jar";
public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2,)";
public static final String BINARY_SCANNER_MAVEN_VERSION = "[25.0.0.2.2]";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we going to use a fixed version always?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per release, yes it will be a fixed version


// The coordinates to use for Open Liberty versions 25.0.0.7 and up
public static final String OL_FEATURELIST_GROUP_ID = "io.openliberty.features";
public static final String OL_FEATURELIST_ARTIFACT_ID = "open_liberty_featurelist";
public static final String OL_FEATURELIST_TYPE = "xml";
// the version number is generated at the point of use
// The following key is used when passing the feature list file to the scanner
public static final String OL_FEATURELIST_KEY = "openLiberty";

// The coordinates to use for WebSphere Liberty versions 25.0.0.7 to 25.0.0.9
// We will use them for releases prior to 25.0.0.7
// io.openliberty.features:websphere_liberty_base__featurelist:xml:25.0.0.7
// io.openliberty.features:websphere_liberty_core__featurelist:xml:25.0.0.7

// The coordinates to use for WebSphere Liberty versions 25.0.0.10 and up
// Publishing stopped with 25.0.0.12 so this value is used for 26.0.0.1 and up.
// com.ibm.websphere.appserver.features:websphere_liberty_base__featurelist:xml:25.0.0.xx
// com.ibm.websphere.appserver.features:websphere_liberty_core__featurelist:xml:25.0.0.xx

// These coordinates are used in different combinations to access WebSphere Liberty feature lists 25.0.0.7 and up
public static final String WS1_FEATURELIST_GROUP_ID = "io.openliberty.features";
public static final String WS2_FEATURELIST_GROUP_ID = "com.ibm.websphere.appserver.features";
public static final String WSBASE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_base__featurelist";
public static final String WSCORE_FEATURELIST_ARTIFACT_ID = "websphere_liberty_core__featurelist";
public static final String WS_FEATURELIST_TYPE = "xml";
// The following keys are used when passing the feature list files to the scanner
public static final String WSBASE_FEATURELIST_KEY = "liberty";
public static final String WSCORE_FEATURELIST_KEY = "libertyCore";


public static final String GENERATED_FEATURES_FILE_NAME = "generated-features.xml";
public static final String GENERATED_FEATURES_FILE_PATH = "configDropins/overrides/" + GENERATED_FEATURES_FILE_NAME;
public static final String GENERATED_FEATURES_DIR_PATH = "configDropins/overrides/";
public static final String GENERATED_FEATURES_FILE_PATH = GENERATED_FEATURES_DIR_PATH + GENERATED_FEATURES_FILE_NAME;
public static final String GENERATED_FEATURES_TEMP_DIR = ".libertyFeatures";
public static final String GENERATED_FEATURES_TEMP_PATH = GENERATED_FEATURES_TEMP_DIR + File.separator + GENERATED_FEATURES_FILE_PATH;
private static final String FEATURE_MODIFIED_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.RequiredFeatureModifiedException";
private static final String FEATURE_CONFLICT_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.FeatureConflictException";
private static final String PROVIDED_FEATURE_EXCEPTION = "com.ibm.websphere.binary.cmdline.exceptions.ProvidedFeatureConflictException";
Expand Down Expand Up @@ -79,13 +112,13 @@ public abstract class BinaryScannerUtil {
public abstract boolean isDebugEnabled();

// The jar file containing the binary scanner code
private File binaryScanner;
private File binaryScannerJar;
private URLClassLoader binaryScannerClassLoader = null;
private Class binaryScannerClass = null;
private Method binaryScannerMethod = null;

public BinaryScannerUtil(File binaryScanner) {
this.binaryScanner = binaryScanner;
this.binaryScannerJar = binaryScanner;
}

/**
Expand Down Expand Up @@ -118,11 +151,11 @@ public BinaryScannerUtil(File binaryScanner) {
* scanner when used in combination with each other. E.g. EE 7 and MP 2.1
*/
public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String> classFiles, Set<String> allClassesDirectories,
String logLocation, String targetJavaEE, String targetMicroProfile, boolean optimize)
String logLocation, String targetJavaEE, String targetMicroProfile, Map featureListFileMap, boolean optimize)
throws PluginExecutionException, NoRecommendationException, RecommendationSetException, FeatureModifiedException,
FeatureUnavailableException, IllegalTargetException, IllegalTargetComboException {
Set<String> featureList = null;
if (binaryScanner != null && binaryScanner.exists()) {
Set<String> generatedFeatureList = null;
if (binaryScannerJar != null && binaryScannerJar.exists()) {
// if we are already generating features for all class files (optimize=true) and
// we are not passing any user specified features (currentFeatureSet is empty)
// we do not need to rerun the binary scanner if it fails
Expand All @@ -138,17 +171,18 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
logLevel = null;
logLocation = null;
}
debug("Calling " + binaryScanner.getName() + " with the following inputs...\n" +
debug("Calling " + binaryScannerJar.getName() + " with the following inputs...\n" +
" binaryInputs: " + binaryInputs + "\n" +
" targetJavaEE: " + targetJavaEE + "\n" +
" targetMicroP: " + targetMicroProfile + "\n" +
" currentFeatures: " + currentFeatureSet + "\n" +
" featureListFileMap: " + featureListFileMap + "\n" +
" logLocation: " + logLocation + "\n" +
" logLevel: " + logLevel + "\n" +
" locale: " + java.util.Locale.getDefault());
featureList = (Set<String>) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile,
currentFeatureSet, logLocation, logLevel, java.util.Locale.getDefault());
for (String s : featureList) {debug(s);};
generatedFeatureList = (Set<String>) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile,
currentFeatureSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault());
for (String s : generatedFeatureList) {debug(s);};
} catch (InvocationTargetException ite) {
// This is the exception from the JVM that indicates there was an exception in the method we
// called through reflection. We must extract the actual exception from the 'cause' field.
Expand All @@ -168,7 +202,7 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
// The list of features from the app is passed in but it contains conflicts
Set<String> conflicts = getFeatures(scannerException);
// always rerun binary scanner in this scenario, this exception only occurs if a current feature list is passed to binary scanner
Set<String> sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile);
Set<String> sampleFeatureList = reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap);
if (sampleFeatureList == null) {
throw new NoRecommendationException(conflicts);
} else {
Expand All @@ -178,7 +212,7 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
// The scanned files conflict with each other or with current features
Set<String> conflicts = getFeatures(scannerException);
// rerun binary scanner with all class files and without the current feature set to get feature recommendations
Set<String> sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile): null;
Set<String> sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap): null;
if (sampleFeatureList == null) {
throw new NoRecommendationException(conflicts);
} else {
Expand All @@ -188,7 +222,7 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
// The scanned files conflict and the scanner suggests modifying some features
Set<String> modifications = getFeatures(scannerException);
// rerun binary scanner with all class files and without the current feature set
Set<String> sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile) : null;
Set<String> sampleFeatureList = reRunIfFailed ? reRunBinaryScanner(allClassesDirectories, logLocation, targetJavaEE, targetMicroProfile, featureListFileMap) : null;
throw new FeatureModifiedException(modifications,
(sampleFeatureList == null) ? getNoSampleFeatureList() : sampleFeatureList, scannerException.getLocalizedMessage());
} else if (scannerException.getClass().getName().equals(FEATURE_NOT_AVAILABLE_EXCEPTION)) {
Expand Down Expand Up @@ -229,13 +263,13 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar: " + loadingException.toString());
}
} else {
if (binaryScanner == null) {
if (binaryScannerJar == null) {
throw new PluginExecutionException("The binary scanner jar location is not defined.");
} else {
throw new PluginExecutionException("Could not find the binary scanner jar at " + binaryScanner.getAbsolutePath());
throw new PluginExecutionException("Could not find the binary scanner jar at " + binaryScannerJar.getAbsolutePath());
}
}
return featureList;
return generatedFeatureList;
}

/**
Expand All @@ -252,9 +286,9 @@ public Set<String> runBinaryScanner(Set<String> currentFeatureSet, List<String>
* @return - a set of features that will allow the application to run in a Liberty server
* @throws PluginExecutionException - any exception that prevents the scanner from running
*/
public Set<String> reRunBinaryScanner(Set<String> allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile)
throws PluginExecutionException {
Set<String> featureList = null;
public Set<String> reRunBinaryScanner(Set<String> allClassesDirectories, String logLocation, String targetJavaEE, String targetMicroProfile,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that runBinaryScanner and rerunBinaryScanner handle the same binary scanner exceptions completely differently
is this intentional ?

example
PROVIDED_FEATURE_EXCEPTION
runBinaryScanner approach:

  1. Throws NoRecommendationException if no alternatives found
  2. Throws RecommendationSetException if alternatives exist

rerunBinaryScanner approach:

Logs debug message: "RuntimeException from re-run of binary scanner"
Returns null

Copy link
Copy Markdown
Contributor Author

@TrevCraw TrevCraw Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is intentional. The contexts to call runBinaryScanner and rerunBinaryScanner are different. We only call rerunBinaryScanner when there was an error from the binary scanner with the initial runBinaryScanner call. The second call to binary scanner in rerunBinaryScanner is intended to get a pure binary scanner recommended feature list for the application (i.e., analyze the whole application and ignore user-specified features). Because the contexts are different, the exceptions from the binary scanner are handled differently.

Map featureListFileMap) throws PluginExecutionException {
Set<String> generatedFeatureList = null;
try {
Method generateFeatureSetMethod = getScannerMethod();
Set<String> binaryInputs = allClassesDirectories;
Expand All @@ -274,21 +308,21 @@ public Set<String> reRunBinaryScanner(Set<String> allClassesDirectories, String
" logLocation: " + logLocation + "\n" +
" logLevel: " + logLevel + "\n" +
" locale: " + java.util.Locale.getDefault());
featureList = (Set<String>) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile,
currentFeaturesSet, logLocation, logLevel, java.util.Locale.getDefault());
for (String s : featureList) {debug(s);};
generatedFeatureList = (Set<String>) generateFeatureSetMethod.invoke(null, binaryInputs, targetJavaEE, targetMicroProfile,
currentFeaturesSet, featureListFileMap, logLocation, logLevel, java.util.Locale.getDefault());
for (String s : generatedFeatureList) {debug(s);};
} catch (InvocationTargetException ite) {
Throwable scannerException = ite.getCause();
if (scannerException.getClass().getName().equals(PROVIDED_FEATURE_EXCEPTION)) {
// this happens when the list of features passed in contains conflicts so now no recommendation possible
debug("RuntimeException from re-run of binary scanner", scannerException); // shouldn't happen
featureList = null;
generatedFeatureList = null;
} else if (scannerException.getClass().getName().equals(FEATURE_CONFLICT_EXCEPTION)) {
// The features in the scanned files conflict with each other, no recommendation possible
featureList = getNoSampleFeatureList();
generatedFeatureList = getNoSampleFeatureList();
} else if (scannerException.getClass().getName().equals(FEATURE_MODIFIED_EXCEPTION)) {
// The features in the scanned files conflict with each other, no recommendation possible
featureList = getNoSampleFeatureList();
generatedFeatureList = getNoSampleFeatureList();
} else {
debug("Exception from rerunning binary scanner.", scannerException);
throw new PluginExecutionException("Error scanning the application for Liberty feature recommendations: " + scannerException.toString());
Expand All @@ -299,9 +333,9 @@ public Set<String> reRunBinaryScanner(Set<String> allClassesDirectories, String
debug("Caused by exception2:"+loadingException.getCause().getClass().getName());
debug("Caused by exception message2:"+loadingException.getCause().getMessage());
}
throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar for recommendations: " + loadingException.toString());
throw new PluginExecutionException("An error occurred when trying to call the binary scanner jar for Liberty feature recommendations: " + loadingException.toString());
}
return featureList;
return generatedFeatureList;
}

private Set<String> getNoSampleFeatureList() {
Expand All @@ -314,7 +348,7 @@ private Set<String> getNoSampleFeatureList() {
private ClassLoader getScannerClassLoader() throws MalformedURLException {
if (binaryScannerClassLoader == null) {
ClassLoader cl = this.getClass().getClassLoader();
binaryScannerClassLoader = new URLClassLoader(new URL[] { binaryScanner.toURI().toURL() }, cl);
binaryScannerClassLoader = new URLClassLoader(new URL[] { binaryScannerJar.toURI().toURL() }, cl);
}
return binaryScannerClassLoader;
}
Expand All @@ -330,10 +364,12 @@ private Class getScannerClass() throws MalformedURLException, ClassNotFoundExcep
private Method getScannerMethod() throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, PluginExecutionException, SecurityException {
if (binaryScannerMethod == null) {
Class driveScan = getScannerClass();
// args: Set<String>, String, String, Set<String>, String, String, Locale
// names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, logLocation, logLevel, locale
// Method name and return type: Set<String> generateFeatureList()
// arg types: Set<String>, String, String, Set<String>, Map, String, String, Locale
// arg names: binaryInputs, targetJavaEE, targetMicroProfile, currentFeatures, featureListFileMap, logLocation, logLevel, locale

binaryScannerMethod = driveScan.getMethod("generateFeatureList", Set.class, String.class, String.class,
Set.class, String.class, String.class, java.util.Locale.class);
Set.class, Map.class, String.class, String.class, java.util.Locale.class);
if (binaryScannerMethod == null) {
throw new PluginExecutionException("Error finding binary scanner method using reflection");
}
Expand Down
Loading
Loading