All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.psr.vulas.java.ArchiveAnalysisManager Maven / Gradle / Ivy

There is a newer version: 3.1.15
Show newest version
/**
 * 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;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.validation.constraints.NotNull;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sap.psr.vulas.FileAnalyzer;
import com.sap.psr.vulas.core.util.CoreConfiguration;
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.FileSearch;
import com.sap.psr.vulas.shared.util.FileUtil;
import com.sap.psr.vulas.shared.util.StopWatch;
import com.sap.psr.vulas.shared.util.VulasConfiguration;


/**
 * Parallelizes the analysis of Java archives.
 * The number of parallel {@link Thread}s as well as a timeout is passed as argument to the constructor.
 */
public class ArchiveAnalysisManager {

	private static final Log log = LogFactory.getLog(ArchiveAnalysisManager.class);

	private ExecutorService pool;
	
	private boolean instrument = false;
	private Application ctx = null;
	private Path workDir = null;
	private Path libDir = null;
	private Path inclDir = null;
	private Map analyzers = new HashMap();
	
	/** Maps {@link JarAnalyzer}s, which implement {@link Callable}, to their respective {@link Future}. */
	private Map> futures = new HashMap>();
	
	private boolean rename = false;
	
	private Map knownDependencies = null;
	
	private long analysisTimeout = -1;

	/**
	 * 

Constructor for ArchiveAnalysisManager.

* * @param _pool_size the number of parallel analysis threads * @param _timeout the timeout in milleseconds to wait for the completion of all analysis tasks (-1 means no timeout) * @param _instr whether or not the Java archives shall be instrumented * @param _ctx the application context in which the analysis takes place (if any) */ public ArchiveAnalysisManager(int _pool_size, long _timeout, boolean _instr, Application _ctx) { this.pool = Executors.newFixedThreadPool(_pool_size); this.analysisTimeout = _timeout; this.instrument = _instr; this.ctx = _ctx; } /** *

Setter for the field instrument.

* * @param _instr a boolean. */ public void setInstrument(boolean _instr) { this.instrument = _instr; } /** *

Setter for the field workDir.

* * @param _p a {@link java.nio.file.Path} object. */ public void setWorkDir(Path _p) { this.setWorkDir(_p, false); } /** *

Setter for the field workDir.

* * @param _p a {@link java.nio.file.Path} object. * @param _create a boolean. */ public void setWorkDir(Path _p, boolean _create) { this.workDir = _p; if(_create) { try { Files.createDirectories(_p); } catch (IOException e) { ArchiveAnalysisManager.log.error("Error while creating dir [" + _p + "]: " + e.getMessage()); } } ArchiveAnalysisManager.log.info("Work dir set to [" + _p + "]"); } /** *

Setter for the field libDir.

* * @param _p a {@link java.nio.file.Path} object. */ public void setLibDir(Path _p) { this.libDir = _p; ArchiveAnalysisManager.log.info("Lib dir set to [" + _p + "]"); } /** *

setIncludeDir.

* * @param _p a {@link java.nio.file.Path} object. */ public void setIncludeDir(Path _p) { this.inclDir = _p; ArchiveAnalysisManager.log.info("Include dir set to [" + _p + "]"); } /** *

getSupportedFileExtensions.

* * @return an array of {@link java.lang.String} objects. */ public static String[] getSupportedFileExtensions() { return new String[] { "jar", "war", "aar" }; } /** *

canAnalyze.

* * @param _file a {@link java.io.File} object. * @return a boolean. */ public static final boolean canAnalyze(File _file) { final String ext = FileUtil.getFileExtension(_file); if(ext == null || ext.equals("")) return false; for(String supported_ext: getSupportedFileExtensions()) { if(supported_ext.equalsIgnoreCase(ext)) return true; } return false; } /** * Determines whether the instrumented JAR is renamed or not. If yes, the new file name follows the following format: * - If app context is provided: [originalJarName]-vulas-[appGroupId]-[appArtifactId]-[appVersion].jar * - Otherwise: [originalJarName]-vulas.jar * * @param _b a boolean. */ public void setRename(boolean _b) { this.rename = _b; } /** * Takes a map of file system paths to {@link Dependency}s. * Entries will be used when instantiating {@link JarAnalyzer}s in {@link #startAnalysis(Set, JarAnalyzer)}. * * @param _deps a {@link java.util.Map} object. */ public void setKnownDependencies(Map _deps) { this.knownDependencies = _deps; } /** * Returns the {@link Dependency} for a given archive, or null if no such archive is known. * The underlying map is built during the execution of the Maven plugin. * * @param _p a {@link java.nio.file.Path} object. * @return a {@link com.sap.psr.vulas.shared.json.model.Dependency} object. */ public Dependency getKnownDependency(Path _p) { if(this.knownDependencies!=null) return this.knownDependencies.get(_p); else return null; } /** * Starts the analysis for all {@link Path}s of the given {@link Map} that have a null value. * As such, it can be used to avoid analyzing archives multiple times. * * @param _paths a {@link java.util.Map} object. * @param _parent a {@link com.sap.psr.vulas.java.JarAnalyzer} object. */ public void startAnalysis(@NotNull Map _paths, JarAnalyzer _parent) { // Split those that have been analyzed already and those that need analysis final Set not_yet_analyzed = new HashSet(); for(Map.Entry entry: _paths.entrySet()) { if(entry.getValue()==null) not_yet_analyzed.add(entry.getKey()); else this.analyzers.put(entry.getKey(), entry.getValue()); } // Analyze if necessary if(!not_yet_analyzed.isEmpty()) { log.info("[" + this.analyzers.size() + "/" + _paths.size() + "] archives already analyzed, the remaining [" + not_yet_analyzed.size() + "] will be analyzed now ..."); this.startAnalysis(not_yet_analyzed, _parent); } else { log.info("All [" + this.analyzers.size() + "/" + _paths.size() + "] archives have been analyzed already"); } } /** * Starts the analysis for all the given {@link Path}s, which must point to either JAR or WAR archives. * * @param _paths a {@link java.util.Set} object. * @param parent a {@link com.sap.psr.vulas.java.JarAnalyzer} object. */ public void startAnalysis(@NotNull Set _paths, JarAnalyzer parent) { // Set the several static attributes of the JarAnalyzer, for all the instances created later JarAnalyzer.setAppContext(this.ctx); // Add all the paths to the classpath, so that the compilation works (if instrumentation is requested). for(Path p: _paths) { try { JarAnalyzer.insertClasspath(p.toString()); } catch (Exception e) { // No problem at all if instrumentation is not requested. // If instrumentation is requested, however, some classes may not compile ArchiveAnalysisManager.log.error("Error while updating the classpath: " + e.getMessage()); } } // Add additional JARs into the classpath (if any) if(this.libDir!=null && (this.libDir.toFile().exists())) { final FileSearch vis = new FileSearch(new String[] {"jar"}); final Set libs = vis.search(this.libDir); for(Path p: libs) { try { JarAnalyzer.insertClasspath(p.toString()); } catch (Exception e) { // No problem at all if instrumentation is not requested. // If instrumentation is requested, however, some classes may not compile ArchiveAnalysisManager.log.error("Error while updating the classpath from lib [" + this.libDir + "]: " + e.getMessage()); } } } // Create temp directory for storing the modified JARs (if any) if(this.instrument && this.workDir==null) { try { workDir = java.nio.file.Files.createTempDirectory("jar_analysis_"); } catch (IOException e) { throw new IllegalStateException("Unable to create work directory", e); } } // Create a JarAnalyzer for all paths, and ask the thread pool to start them for(Path p: _paths) { try { JarAnalyzer ja = null; if(p.toString().endsWith("jar")) { ja = new JarAnalyzer(); } else if(p.toString().endsWith("war")) { ja = new WarAnalyzer(); ((WarAnalyzer)ja).setIncludeDir(this.inclDir); } else if(p.toString().endsWith("aar")) { ja = new AarAnalyzer(); } else { ArchiveAnalysisManager.log.warn("File extension not supported (only JAR, WAR, AAR): " + p); continue; } if(parent!=null) ja.setParent(parent); ja.setRename(this.rename); ja.setWorkDir(this.workDir); if(this.getKnownDependency(p)!=null) ja.setLibraryId(this.getKnownDependency(p).getLib().getLibraryId()); ja.analyze(p.toFile()); ja.setInstrument(this.instrument); // To be called after analyze, since instrument uses the URL member this.analyzers.put(p, ja); // Execute the analyzer final Future future = this.pool.submit(ja); this.futures.put(ja, future); } catch (Exception e) { ArchiveAnalysisManager.log.error("Error while analyzing path [" + p + "]: " + e.getMessage()); } } final StopWatch sw = new StopWatch("Analysis of [" + futures.size() + "] Java archives").setTotal(futures.size()).start(); this.pool.shutdown(); // Don't accept new tasks, wait for the completion of existing ones try { while (!this.pool.awaitTermination(10, TimeUnit.SECONDS)) { final Map> open_tasks = this.getOpenTasks(); final long sw_runtime = sw.getRuntimeMillis(); // There are remaining tasks, and a timeout has been configured and reached if(!open_tasks.isEmpty() && this.analysisTimeout!=-1 && sw_runtime > this.analysisTimeout) { ArchiveAnalysisManager.log.warn("Timeout of [" + this.analysisTimeout + "ms] reached, the following [" + open_tasks.size() + "] non-completed analysis tasks will be canceled:"); for(JarAnalyzer ja: open_tasks.keySet()) { ArchiveAnalysisManager.log.info(" " + ja); open_tasks.get(ja).cancel(true); } throw new JarAnalysisException("Timeout of [" + this.analysisTimeout + "ms] reached, [" + open_tasks.size() + "] have been canceled"); } // There are remaining tasks, but no timeout is set or reached else if(!open_tasks.isEmpty()) { ArchiveAnalysisManager.log.info("Waiting for the completion of [" + open_tasks.size() + "] analysis tasks"); for(JarAnalyzer ja: open_tasks.keySet()) ArchiveAnalysisManager.log.debug(" " + ja); } } sw.stop(); } catch (JarAnalysisException jae) { sw.stop(jae); } catch (InterruptedException e) { sw.stop(e); } } /** * Returns all analysis {@link Future}s that are not (yet) done. * @see Future#isDone() * @return */ private final Map> getOpenTasks() { final Map> open_tasks = new HashMap>(); for(JarAnalyzer ja: this.futures.keySet()) { if(!this.futures.get(ja).isDone()) { open_tasks.put(ja, this.futures.get(ja)); } } return open_tasks; } /** *

Getter for the field analyzers.

* * @return a {@link java.util.Set} object. */ public Set getAnalyzers() { final HashSet analyzers = new HashSet(); for(JarAnalyzer ja : this.analyzers.values()) { analyzers.add(ja); if(ja.hasChilds()) { final Set fas = ja.getChilds(true); for(FileAnalyzer fa: fas) if(fa instanceof JarAnalyzer) analyzers.add((JarAnalyzer)fa); } } return analyzers; } /** * Returns the analyzer used to analyze a Java archive whose path ends with the given sub-path _p (the first match is taken), null if no such analyzer can be found. * * @param _p a {@link java.nio.file.Path} object. * @return a {@link com.sap.psr.vulas.java.JarAnalyzer} object. */ public JarAnalyzer getAnalyzerForSubpath(Path _p) { JarAnalyzer ja = null; for(Path p: this.analyzers.keySet()) { if(p.endsWith(_p)) { ja = this.analyzers.get(p); break; } } return ja; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy