com.sap.psr.vulas.java.tasks.JavaBomTask Maven / Gradle / Ivy
/**
* This file is part of Eclipse Steady.
*
* 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
*
* http://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.
*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved.
*/
package com.sap.psr.vulas.java.tasks;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sap.psr.vulas.ConstructId;
import com.sap.psr.vulas.DirAnalyzer;
import com.sap.psr.vulas.FileAnalysisException;
import com.sap.psr.vulas.FileAnalyzer;
import com.sap.psr.vulas.FileAnalyzerFactory;
import com.sap.psr.vulas.core.util.CoreConfiguration;
import com.sap.psr.vulas.goals.GoalConfigurationException;
import com.sap.psr.vulas.goals.GoalExecutionException;
import com.sap.psr.vulas.java.ArchiveAnalysisManager;
import com.sap.psr.vulas.java.JarAnalyzer;
import com.sap.psr.vulas.java.JavaId;
import com.sap.psr.vulas.shared.enums.GoalClient;
import com.sap.psr.vulas.shared.enums.ProgrammingLanguage;
import com.sap.psr.vulas.shared.enums.Scope;
import com.sap.psr.vulas.shared.json.model.Application;
import com.sap.psr.vulas.shared.json.model.Dependency;
import com.sap.psr.vulas.shared.util.DependencyUtil;
import com.sap.psr.vulas.shared.util.StringList;
import com.sap.psr.vulas.shared.util.StringUtil;
import com.sap.psr.vulas.shared.util.ThreadUtil;
import com.sap.psr.vulas.shared.util.VulasConfiguration;
import com.sap.psr.vulas.tasks.AbstractBomTask;
/**
* JavaBomTask class.
*
*/
public class JavaBomTask extends AbstractBomTask {
private static final Log log = LogFactory.getLog(JavaBomTask.class);
private static final String[] EXT_FILTER = new String[] { "jar", "war", "class", "java", "aar" };
private String[] appPrefixes = null;
private StringList appJarNames = null;
private static final List pluginGoalClients = Arrays.asList(GoalClient.MAVEN_PLUGIN, GoalClient.GRADLE_PLUGIN);
/** {@inheritDoc} */
@Override
public Set getLanguage() { return new HashSet(Arrays.asList(new ProgrammingLanguage[] { ProgrammingLanguage.JAVA })); }
/**
* Returns true if the configuration setting {@link CoreConfiguration#APP_PREFIXES} shall be considered, false otherwise.
*/
private final boolean useAppPrefixes() {
return this.appPrefixes!=null && !this.isOneOfGoalClients(pluginGoalClients);
}
/**
* Returns true if the configuration setting {@link CoreConfiguration#APP_PREFIXES} shall be considered, false otherwise.
*/
private final boolean useAppJarNames() {
return this.appJarNames!=null && !this.isOneOfGoalClients(pluginGoalClients);
}
/** {@inheritDoc} */
@Override
public void configure(VulasConfiguration _cfg) throws GoalConfigurationException {
super.configure(_cfg);
// App constructs identified using package prefixes
this.appPrefixes = _cfg.getStringArray(CoreConfiguration.APP_PREFIXES, null);
// Print warning message in case the setting is used as part of the Maven plugin
if(this.appPrefixes!=null && this.isOneOfGoalClients(pluginGoalClients)) {
log.warn("Configuration setting [" + CoreConfiguration.APP_PREFIXES + "] ignored when running the goal as Maven plugin");
this.appPrefixes = null;
}
// App constructs identified using JAR file name patterns (regex)
final String[] app_jar_names = _cfg.getStringArray(CoreConfiguration.APP_JAR_NAMES, null);
if(app_jar_names!=null) {
// Print warning message in case the setting is used as part of the Maven plugin
if( this.isOneOfGoalClients(pluginGoalClients)) {
log.warn("Configuration setting [" + CoreConfiguration.APP_JAR_NAMES + "] ignored when running the goal as Maven plugin");
this.appJarNames = null;
}
else {
this.appJarNames = new StringList();
this.appJarNames.addAll(app_jar_names);
}
}
// CLI: Only one of appPrefixes and appJarNames can be used
if(!this.isOneOfGoalClients(pluginGoalClients)) {
if(this.appPrefixes!=null && this.appJarNames!=null) {
throw new GoalConfigurationException("Exactly one of the configuration settings [" + CoreConfiguration.APP_PREFIXES + "] and [" + CoreConfiguration.APP_JAR_NAMES + "] must be set");
}
else if(this.appPrefixes==null && this.appJarNames==null) {
throw new GoalConfigurationException("Exactly one of the configuration settings [" + CoreConfiguration.APP_PREFIXES + "] and [" + CoreConfiguration.APP_JAR_NAMES + "] must be set");
}
}
}
/** {@inheritDoc} */
@Override
public void execute() throws GoalExecutionException {
// All app constructs
final Set app_constructs = new HashSet();
// Dependency files
final Map dep_files = new HashMap();
if(this.getKnownDependencies()!=null) {
for(Path p: this.getKnownDependencies().keySet())
dep_files.put(p, null);
}
// 1) Find app constructs by looping over all app paths
if(this.hasSearchPath()) {
for(Path p: this.getSearchPath()) {
log.info("Searching for Java constructs in search path [" + p + "] with filter [" + StringUtil.join(EXT_FILTER, ", ") + "] ...");
final FileAnalyzer fa = FileAnalyzerFactory.buildFileAnalyzer(p.toFile(), EXT_FILTER);
// Prefixes or jar name regex: Filter JAR constructs
if(this.useAppPrefixes() || this.useAppJarNames()) {
// All analyzers to loop over
final Set analyzers = new HashSet();
// Add child analyzers and analyzer itself (except DirAnalyzer)
if(fa.hasChilds())
analyzers.addAll(fa.getChilds(true));
if(!(fa instanceof DirAnalyzer))
analyzers.add(fa);
// Log
int count = 0;
if(this.useAppPrefixes()) {
log.info("Looping over Java archive analyzers to separate application and dependency code using package prefix(es) [" + StringUtil.join(this.appPrefixes, ", ") + "] ...");
} else if(this.useAppJarNames()) {
log.info("Looping over Java archive analyzers to separate application and dependency code using filename pattern(s) [" + this.appJarNames.toString(", ") + "] ...");
}
// Loop over all analyzers
for(FileAnalyzer fa2: analyzers) {
try {
if(fa2 instanceof JarAnalyzer) {
final JarAnalyzer ja = (JarAnalyzer)fa2;
// Prefixes
if(this.useAppPrefixes()) {
final Set constructs = JavaId.filter(fa2.getConstructs().keySet(), this.appPrefixes);
// Constructs match to the prefixes
if(constructs!=null && constructs.size()>0) {
app_constructs.addAll(constructs);
// Exclusively app constructs
if(constructs.size()==fa2.getConstructs().size()) {
log.info(StringUtil.padLeft(++count, 4) + " [" + StringUtil.padLeft(ja.getFileName(), 30) + "]: All [" + fa2.getConstructs().size() + "] constructs matched prefix(es): Constructs added to application, file NOT added as dependency");
}
// Mixed archive: Add as dependency
else {
log.info(StringUtil.padLeft(++count, 4) + " [" + StringUtil.padLeft(ja.getFileName(), 30) + "]: [" + constructs.size() + "/" + fa2.getConstructs().size() + "] constructs matched prefix(es): Constructs added to application, file added as dependency");
dep_files.put(ja.getPath(), ja);
}
}
// No constructs match to the prefixes
else {
log.info(StringUtil.padLeft(++count, 4) + " [" + StringUtil.padLeft(ja.getFileName(), 30) + "]: None of the [" + fa2.getConstructs().size() + "] constructs matched prefix(es): No constructs added to application, file added as dependency");
dep_files.put(ja.getPath(), ja);
}
}
// Jar name regex
else if(this.useAppJarNames()) {
// Belongs to application
if(this.appJarNames.contains(ja.getFileName(), StringList.ComparisonMode.PATTERN, StringList.CaseSensitivity.CASE_INSENSITIVE)) {
log.info(StringUtil.padLeft(++count, 4) + " [" + StringUtil.padLeft(ja.getFileName(), 30) + "]: Filename matches pattern(s), all of its [" + fa2.getConstructs().size() + "] constructs added to application");
app_constructs.addAll(fa2.getConstructs().keySet());
}
// Dependency
else {
log.info(StringUtil.padLeft(++count, 4) + " [" + StringUtil.padLeft(ja.getFileName(), 30) + "]: Filename does not match pattern(s), file added as dependency");
dep_files.put(ja.getPath(), ja);
}
}
}
// Important: What is in java and class files is always part of the app
else {
app_constructs.addAll(fa2.getConstructs().keySet());
}
} catch (FileAnalysisException e) {
log.error(e.getMessage(), e);
}
}
}
// No prefixes and jar name regex: Add all constructs to app
else {
try {
app_constructs.addAll(fa.getConstructs().keySet());
} catch (FileAnalysisException e) {
log.error(e.getMessage(), e);
}
}
}
}
// 2) Analyze all of the JAR/WAR files
final Set app_dependencies = new HashSet();
final long timeout = this.vulasConfiguration.getConfiguration().getLong(CoreConfiguration.JAR_TIMEOUT, -1);
final int no_threads = ThreadUtil.getNoThreads(this.vulasConfiguration, 2);
final ArchiveAnalysisManager mgr = new ArchiveAnalysisManager(no_threads, timeout, false, this.getApplication());
mgr.setKnownDependencies(this.getKnownDependencies());
mgr.startAnalysis(dep_files, null);
// Loop over all analyzers created above and add to app dependencies
final Set analyzers = mgr.getAnalyzers();
for(JarAnalyzer ja: analyzers) {
try {
// Prefixes can be used: Filter JAR constructs
if(this.useAppPrefixes()) {
final Set constructs = JavaId.filter(ja.getConstructs().keySet(), this.appPrefixes);
// Constructs match to the prefixes
if(constructs!=null && constructs.size()>0) {
app_constructs.addAll(constructs);
// Exclusively app constructs
if(constructs.size()==ja.getConstructs().size()) {
log.info("All of the [" + ja.getConstructs().size() + "] constructs from [" + ja.getFileName() + "] matched to prefix [" + StringUtil.join(this.appPrefixes, ", ") + "]: Constructs added to application, file removed from dependencies");
}
// Mixed archive: Add as dependency
else {
log.info("[" + constructs.size() + "/" + ja.getConstructs().size() + "] constructs from [" + ja.getFileName() + "] matched to prefix [" + StringUtil.join(this.appPrefixes, ", ") + "]: Constructs added to application, file kept as dependency");
app_dependencies.add(ja);
}
}
// No constructs match to the prefixes
else {
log.info("None of the [" + ja.getConstructs().size() + "] constructs from [" + ja.getFileName() + "] matched to prefix [" + StringUtil.join(this.appPrefixes, ", ") + "]: No constructs added to application, file kept as dependency");
app_dependencies.add(ja);
}
}
// Prefixes cannot be used: Add JAR as dependency unless jar name regex exists
else {
if(this.useAppJarNames() && this.appJarNames.contains(ja.getFileName(), StringList.ComparisonMode.PATTERN, StringList.CaseSensitivity.CASE_INSENSITIVE)) {
app_constructs.addAll(ja.getConstructs().keySet());
} else {
app_dependencies.add(ja);
}
}
} catch (FileAnalysisException e) {
log.error(e.getMessage(), e);
}
}
// Update application
final Application a = this.getApplication();
a.addConstructs(ConstructId.getSharedType(app_constructs));
// Loop all JAR analyzers and add a corresponding dependency
for(JarAnalyzer ja: app_dependencies) {
try {
final Dependency app_dep = new Dependency();
app_dep.setLib(ja.getLibrary());
app_dep.setApp(this.getApplication());
app_dep.setFilename(ja.getFileName());
app_dep.setPath(ja.getPath().toString());
// Dependency known to a package manager (if any)
Dependency known_dep = null;
if(ja.getParent()!=null){
known_dep = mgr.getKnownDependency(ja.getParent().getPath());
}
else {
known_dep = mgr.getKnownDependency(ja.getPath());
}
// Take information from known dependency
app_dep.setScope( (known_dep!=null ? known_dep.getScope() : Scope.RUNTIME) );
app_dep.setTransitive( (ja.getParent()!= null? new Boolean(true) :(known_dep!=null ? new Boolean(known_dep.getTransitive()) : new Boolean(false)) ) );
app_dep.setDeclared( ((known_dep!=null && ja.getParent()==null) ? new Boolean(true): new Boolean(false)) );
// Set the parent (if any)
if(known_dep!=null && known_dep.getParent()!=null) {
// Complete the draft parent dependency with library info
for(JarAnalyzer ja2: app_dependencies) {
//TODO: As soon as we add the relative path to the JARAnalyzer, it must also be used to uniquely identify the parent and set all its fields
if(ja2.getPath().toString().equals(known_dep.getParent().getPath())) {
known_dep.getParent().setLib(ja2.getLibrary());
break;
}
}
app_dep.setParent(known_dep.getParent());
}
a.addDependency(app_dep);
} catch (FileAnalysisException e) {
log.error(e.getMessage(), e);
}
}
// Get a clean set of dependencies
final Set no_dupl_deps = DependencyUtil.removeDuplicateLibraryDependencies(a.getDependencies());
a.setDependencies(no_dupl_deps);
// Fix parents on dependencies that were removed (this block should be removed once we use the relativePath and we get a dependency tree representing the actual one
for(Dependency d : a.getDependencies()) {
if(d.getParent()!=null ) {
for(Dependency existing: a.getDependencies()) {
//TODO: as soon as we add the relative path to the JARAnalyzer, it must also be used to uniquely identify the parent
if(existing.getLib().getDigest().equals(d.getParent().getLib().getDigest())) {
d.getParent().setLib(existing.getLib());
d.getParent().setPath(existing.getPath());
d.getParent().setFilename(existing.getFilename());
break;
}
}
}
}
// Check whether the parent-child dependency relationships are consistent
final boolean consistent_deps = DependencyUtil.isValidDependencyCollection(a);
if(!consistent_deps) {
throw new GoalExecutionException("Inconsistent application dependencies cannot be uploaded", null);
}
// Set the one to be returned
this.setCompletedApplication(a);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy