Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import io.openliberty.tools.common.plugins.config.ServerConfigXmlDocument
import io.openliberty.tools.common.plugins.config.XmlDocument
import io.openliberty.tools.common.plugins.util.BinaryScannerUtil
import static io.openliberty.tools.common.plugins.util.BinaryScannerUtil.*;
import io.openliberty.tools.common.plugins.util.GenerateFeaturesUtil;
import io.openliberty.tools.common.plugins.util.GenerateFeaturesUtil.GenerateFeaturesException;
import io.openliberty.tools.common.plugins.util.PluginExecutionException
import io.openliberty.tools.common.plugins.util.ServerFeatureUtil
import io.openliberty.tools.common.plugins.util.ServerFeatureUtil.FeaturesPlatforms
Expand All @@ -37,11 +39,6 @@ import javax.xml.transform.TransformerException

class GenerateFeaturesTask extends AbstractFeatureTask {

public static final String HEADER = "This file was generated by the Liberty Gradle Plugin and will be overwritten on subsequent runs of the generateFeatures task." + "\n It is recommended that you do not edit this file and that you commit this file to your version control.";
public static final String GENERATED_FEATURES_COMMENT = "The following features were generated based on API usage detected in your application";
public static final String NO_NEW_FEATURES_COMMENT = "No additional features generated";
public static final String NO_CLASS_FILES_WARNING = "Could not find class files to generate features against. Liberty features will not be generated. Ensure your project has first been compiled.";

// Default value of the optimize task option
private static final boolean DEFAULT_OPTIMIZE = true;
// Default value of the generateToSrc option
Expand All @@ -50,16 +47,6 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
// The executable file used to scan binaries for the Liberty features they use.
private File binaryScanner;

/**
* Generating features is performed relative to a certain server. We only generate features
* that are missing from a server config. By default we generate features that are missing
* from the server directory in build/wlp/usr/servers/<server name>.
* If generateToSrc is specified then we generate features which are missing from the Liberty
* config specified in the src directory src/main/liberty/config.
* We will select one server config as the context of this operation.
*/
private File generationContextDir;

GenerateFeaturesTask() {
configure({
description = 'Generate the features used by an application and add to the configuration of a Liberty server'
Expand Down Expand Up @@ -94,6 +81,7 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
void generateFeatures() {
binaryScanner = getBinaryScannerJarFromRepository();
BinaryScannerHandler binaryScannerHandler = new BinaryScannerHandler(binaryScanner);
logger.debug("Binary scanner jar: " + binaryScanner.getName());

if (optimize == null) {
optimize = DEFAULT_OPTIMIZE;
Expand All @@ -103,164 +91,18 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
}

initializeConfigDirectory();
// The server.configDirectory is in the src directory. Otherwise generate for the build dir.
generationContextDir = generateToSrc ? server.configDirectory : getServerDir(project);

logger.debug("--- Generate Features values ---");
logger.debug("optimize generate features: " + optimize);
logger.debug("generate to src or build: " + generationContextDir);
if (classFiles != null && !classFiles.isEmpty()) {
logger.debug("Generate features for the following class files: " + classFiles);
}

// TODO add support for env variables
// commented out for now as the current logic depends on the server dir existing and specifying features with env variables is an edge case
/* def serverDirectory = getServerDir(project);
def libertyDirPropertyFiles;
try {
libertyDirPropertyFiles = getLibertyDirectoryPropertyFiles(getInstallDir(project), getUserDir(project), serverDirectory);
} catch (IOException x) {
logger.debug("Exception reading the server property files", e);
logger.error("Error attempting to generate server feature list. Ensure your user account has read permission to the property files in the server installation directory.");
return;
} */

// get existing server features from source directory
ServerFeatureUtil servUtil = getServerFeatureUtil(true, null);
Set<String> generatedFiles = new HashSet<String>();
generatedFiles.add(GENERATED_FEATURES_FILE_NAME);

Set<String> existingFeatures = getServerFeatures(servUtil, generatedFiles, optimize);
logger.debug("Existing features:" + existingFeatures);
Set<String> nonCustomFeatures = new HashSet<String>(); // binary scanner only handles actual Liberty features
for (String feature : existingFeatures) { // custom features are "usr:feature-1.0" or "myExt:feature-2.0"
if (!feature.contains(":")) nonCustomFeatures.add(feature);
}
logger.debug("Non-custom features:" + nonCustomFeatures);

Set<String> scannedFeatureList;
String eeVersion = null;
String mpVersion = null;
try {
Set<String> directories = getClassesDirectories();
if (directories.isEmpty() && (classFiles == null || classFiles.isEmpty())) {
// log as warning and continue to call binary scanner to detect conflicts in user specified features
logger.warn(NO_CLASS_FILES_WARNING);
}
eeVersion = getEEVersion(project);
mpVersion = getMPVersion(project);

String logLocation = project.getBuildDir().getCanonicalPath();
String eeVersionArg = composeEEVersion(eeVersion);
String mpVersionArg = composeMPVersion(mpVersion);
scannedFeatureList = binaryScannerHandler.runBinaryScanner(nonCustomFeatures, classFiles, directories, logLocation, eeVersionArg, mpVersionArg, optimize);
} catch (BinaryScannerUtil.NoRecommendationException noRecommendation) {
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE3, noRecommendation.getConflicts()));
} catch (BinaryScannerUtil.FeatureModifiedException featuresModified) {
Set<String> userFeatures = (optimize) ? existingFeatures :
getServerFeatures(servUtil, generatedFiles, true); // user features excludes generatedFiles
Set<String> modifiedSet = featuresModified.getFeatures(); // a set that works after being modified by the scanner

if (modifiedSet.containsAll(userFeatures)) {
// none of the user features were modified, only features which were generated earlier.
logger.debug("FeatureModifiedException, modifiedSet containsAll userFeatures, pass modifiedSet on to generateFeatures");
// features were modified to get a working set with the application's API usage, display warning to users and use modified set
logger.warn(featuresModified.getMessage());
scannedFeatureList = modifiedSet;
} else {
Set<String> allAppFeatures = featuresModified.getSuggestions(); // suggestions are scanned from binaries
allAppFeatures.addAll(userFeatures); // scanned plus configured features were detected to be in conflict
logger.debug("FeatureModifiedException, combine suggestions from scanner with user features in error msg");
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE1, allAppFeatures, modifiedSet));
}
} catch (BinaryScannerUtil.RecommendationSetException showRecommendation) {
if (showRecommendation.isExistingFeaturesConflict()) {
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE2, showRecommendation.getConflicts(), showRecommendation.getSuggestions()));
}
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE1, showRecommendation.getConflicts(), showRecommendation.getSuggestions()));
} catch (BinaryScannerUtil.FeatureUnavailableException featureUnavailable) {
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_CONFLICT_MESSAGE5, featureUnavailable.getConflicts(), featureUnavailable.getMPLevel(),
featureUnavailable.getEELevel(), featureUnavailable.getUnavailableFeatures()));
} catch (BinaryScannerUtil.IllegalTargetComboException illegalCombo) {
throw new GradleException(String.format(BinaryScannerUtil.BINARY_SCANNER_INVALID_COMBO_MESSAGE, eeVersion, mpVersion));
} catch (BinaryScannerUtil.IllegalTargetException illegalTargets) {
String messages = buildInvalidArgExceptionMessage(illegalTargets.getEELevel(), illegalTargets.getMPLevel(), eeVersion, mpVersion);
throw new GradleException(messages);
} catch (PluginExecutionException x) {
// throw an error when there is a problem not caught in runBinaryScanner()
Object o = x.getCause();
if (o != null) {
logger.debug("Caused by exception:" + x.getCause().getClass().getName());
logger.debug("Caused by exception message:" + x.getCause().getMessage());
}
throw new GradleException("Failed to generate a working set of features. " + x.getMessage(), x);
}

def missingLibertyFeatures = new HashSet<String>();
if (scannedFeatureList != null) {
missingLibertyFeatures.addAll(scannedFeatureList);

servUtil.setLowerCaseFeatures(false);
// get set of user defined features so they can be omitted from the generated file that will be written
Set<String> userDefinedFeatures = optimize ? existingFeatures : new HashSet<String>();
if (!optimize) {
FeaturesPlatforms fp = servUtil.getServerFeatures(generationContextDir, server.serverXmlFile, new HashMap<String, File>(), generatedFiles);
if (fp != null) {
userDefinedFeatures = fp.getFeatures();
}
}
logger.debug("User defined features:" + userDefinedFeatures);
servUtil.setLowerCaseFeatures(true);
if (!userDefinedFeatures.isEmpty()) {
missingLibertyFeatures.removeAll(userDefinedFeatures);
}
}
logger.debug("Features detected by binary scanner which are not in server.xml : " + missingLibertyFeatures);

// generate the new features into an xml file in the correct context directory
def generatedXmlFile = new File(generationContextDir, GENERATED_FEATURES_FILE_PATH);
GenerateFeaturesHandler generateFeaturesHandler = new GenerateFeaturesHandler(project,
binaryScannerHandler, server.configDirectory, getServerDir(project), classFiles, GenerateFeaturesUtil.HEADER_G);
try {
if (missingLibertyFeatures.size() > 0) {
Set<String> existingGeneratedFeatures = getGeneratedFeatures(servUtil, generatedXmlFile);
if (!missingLibertyFeatures.equals(existingGeneratedFeatures)) {
// Create specialized server.xml
ServerConfigXmlDocument configDocument = ServerConfigXmlDocument.newInstance();
configDocument.createComment(HEADER);
Element featureManagerElem = configDocument.createFeatureManager();
configDocument.createComment(featureManagerElem, GENERATED_FEATURES_COMMENT);
for (String missing : missingLibertyFeatures) {
logger.debug(String.format("Adding missing feature %s to %s.", missing, GENERATED_FEATURES_FILE_PATH));
configDocument.createFeature(missing);
}
// Generate log message before writing file as the file change event kicks off other dev mode actions
logger.lifecycle("Generated the following features: " + missingLibertyFeatures);
// use logger.lifecycle so that message appears without --info tag on
configDocument.writeXMLDocument(generatedXmlFile);
logger.debug("Created file " + generatedXmlFile);
} else {
logger.lifecycle("Regenerated the following features: " + missingLibertyFeatures);
// use logger.lifecycle so that message appears without --info tag on
}
} else {
logger.lifecycle("No additional features were generated.");
if (generatedXmlFile.exists()) {
// generated-features.xml exists but no additional features were generated
// create empty features list with comment
ServerConfigXmlDocument configDocument = ServerConfigXmlDocument.newInstance();
configDocument.createComment(HEADER);
Element featureManagerElem = configDocument.createFeatureManager();
configDocument.createComment(featureManagerElem, NO_NEW_FEATURES_COMMENT);
configDocument.writeXMLDocument(generatedXmlFile);
}
}
} catch (ParserConfigurationException | TransformerException | IOException e) {
logger.debug("Exception creating the server features file", e);
throw new GradleException("Automatic generation of features failed. Error attempting to create the " + GENERATED_FEATURES_FILE_NAME + ". Ensure your id has write permission to the server installation directory.", e);
generateFeaturesHandler.generateFeatures(optimize, generateToSrc);
} catch (GenerateFeaturesException e) {
throw new GradleException(e.getMessage(), e.getCause());
}
}

// Get the features from the server config and optionally exclude the specified config files from the search.
private Set<String> getServerFeatures(ServerFeatureUtil servUtil, Set<String> generatedFiles, boolean excludeGenerated) {
private Set<String> getServerFeaturesGradle(ServerFeatureUtil servUtil, File generationContextDir, Set<String> generatedFiles, boolean excludeGenerated) {
servUtil.setLowerCaseFeatures(false);
// if optimizing, ignore generated files when passing in existing features to binary scanner
FeaturesPlatforms fp = servUtil.getServerFeatures(generationContextDir, server.serverXmlFile, new HashMap<String, File>(), excludeGenerated ? generatedFiles : null); // pass generatedFiles to exclude them
Expand All @@ -270,16 +112,6 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
return existingFeatures;
}

// returns the features specified in the generated-features.xml file
private Set<String> getGeneratedFeatures(ServerFeatureUtil servUtil, File generatedFeaturesFile) {
servUtil.setLowerCaseFeatures(false);
FeaturesPlatforms fp = servUtil.getServerXmlFeatures(new FeaturesPlatforms(), generationContextDir,
generatedFeaturesFile, null, null);
Set<String> genFeatSet = fp == null ? new HashSet<String>() : fp.getFeatures();
servUtil.setLowerCaseFeatures(true);
return genFeatSet;
}

/**
* Gets the binary scanner jar file from the local cache.
* Downloads it first from connected repositories such as Maven Central if a newer release is available than the cached version.
Expand All @@ -301,7 +133,7 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
}
}

private Set<String> getClassesDirectories() {
private Set<String> getClassesDirectoriesGradle() {
Set<String> classesDirectories = new ArrayList<String>();
project.sourceSets.main.getOutput().getClassesDirs().each {
if (it.exists()) {
Expand Down Expand Up @@ -362,6 +194,61 @@ class GenerateFeaturesTask extends AbstractFeatureTask {
return (currentVer.compareTo(newVer) < 0);
}

private class GenerateFeaturesHandler extends GenerateFeaturesUtil {
public GenerateFeaturesHandler(Object project, BinaryScannerUtil binaryScannerHandler, File configDirectory, File serverDirectory, List<String> classFiles, String header) {
super(project, binaryScannerHandler, configDirectory, serverDirectory, classFiles, header);
}
@Override
public ServerFeatureUtil getServerFeatureUtil(boolean suppress, Map files) {
return GenerateFeaturesTask.this.getServerFeatureUtil(suppress, files);
}
@Override
public Set<String> getServerFeatures(ServerFeatureUtil servUtil, File generationContextDir, Set<String> generatedFiles, boolean excludeGenerated) {
return getServerFeaturesGradle(servUtil, generationContextDir, generatedFiles, excludeGenerated);
}
@Override
public Set<String> getClassesDirectories(List projects) throws GenerateFeaturesException {
return getClassesDirectoriesGradle();
}
@Override
public List<Object> getProjectList(Object project) {
return Collections.singletonList(project);
}
@Override
public String getEEVersion(List projects) {
return GenerateFeaturesTask.this.getEEVersion(projects.get(0));
}
@Override
public String getMPVersion(List projects) {
return GenerateFeaturesTask.this.getMPVersion(projects.get(0));
}
@Override
public String getLogLocation(Object project) {
return project.getBuildDir().getCanonicalPath();
}
@Override
public File getServerXmlFile() {
return server.serverXmlFile;
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void debug(String msg, Throwable t) {
logger.debug(msg, t);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void info(String msg) {
// use logger.lifecycle so that message appears without --info option on
logger.lifecycle(msg);
}
}

// Define the logging functions of the binary scanner handler and make it available in this plugin
private class BinaryScannerHandler extends BinaryScannerUtil {
BinaryScannerHandler(File scannerFile) {
Expand Down
Loading