/dtkit/src/main/java/org/jenkinsci/plugins/dtkit/DTKitBuilder.java
Java | 445 lines | 345 code | 64 blank | 36 comment | 45 complexity | c7a1dbb913898550b9465340d777f0fa MD5 | raw file
1package org.jenkinsci.plugins.dtkit;
2
3import com.google.inject.AbstractModule;
4import com.google.inject.Guice;
5import com.google.inject.Singleton;
6import hudson.DescriptorExtensionList;
7import hudson.Extension;
8import hudson.FilePath;
9import hudson.Launcher;
10import hudson.Util;
11import hudson.model.AbstractBuild;
12import hudson.model.AbstractProject;
13import hudson.model.BuildListener;
14import hudson.model.Descriptor;
15import hudson.model.ParameterValue;
16import hudson.model.ParametersAction;
17import hudson.model.Result;
18import hudson.model.StringParameterValue;
19import hudson.tasks.BuildStepDescriptor;
20import hudson.tasks.BuildStepMonitor;
21import hudson.tasks.Builder;
22import java.io.File;
23import java.io.IOException;
24import java.util.ArrayList;
25import java.util.List;
26import java.util.ListIterator;
27import net.sf.json.JSONObject;
28import org.jenkinsci.lib.dtkit.descriptor.CoverageTypeDescriptor;
29import org.jenkinsci.lib.dtkit.descriptor.MeasureTypeDescriptor;
30import org.jenkinsci.lib.dtkit.descriptor.TestTypeDescriptor;
31import org.jenkinsci.lib.dtkit.descriptor.ViolationsTypeDescriptor;
32import org.jenkinsci.lib.dtkit.type.CoverageType;
33import org.jenkinsci.lib.dtkit.type.MeasureType;
34import org.jenkinsci.lib.dtkit.type.MetricsType;
35import org.jenkinsci.lib.dtkit.type.TestType;
36import org.jenkinsci.lib.dtkit.type.ViolationsType;
37import org.jenkinsci.plugins.dtkit.service.DTKitBuilderConversionService;
38import org.jenkinsci.plugins.dtkit.service.DTKitBuilderLog;
39import org.jenkinsci.plugins.dtkit.service.DTKitBuilderValidationService;
40import org.jenkinsci.plugins.dtkit.service.DTKitReportProcessingService;
41import org.jenkinsci.plugins.dtkit.transformer.DTKitBuilderToolInfo;
42import org.jenkinsci.plugins.dtkit.transformer.DTKitBuilderTransformer;
43import org.kohsuke.stapler.StaplerRequest;
44
45/**
46 * @author Gregory Boissinot
47 */
48public class DTKitBuilder extends Builder {
49
50 private final String rootFolder;
51 private transient final String generatedFolder;
52 private transient final String generatedTests;
53 private transient final String generatedCoverage;
54 private transient final String generatedMeasures;
55 private transient final String generatedViolations;
56
57
58 private TestType[] tests;
59 private CoverageType[] coverages;
60 private ViolationsType[] violations;
61 private MeasureType[] measures;
62
63 private static final String DEFAULT_ROOT_PATH = "generatedDTKITFiles";
64 private static final String DEFAULT_TEST_PATH = "/TESTS";
65 private static final String DEFAULT_COVERAGE_PATH = "/COVERAGE";
66 private static final String DEFAULT_MEASURES_PATH = "/MEASURES";
67 private static final String DEFAULT_VIOLATIONS_PATH = "/VIOLATIONS";
68
69 /**
70 * Represents checkbox value in plugin configuration page
71 */
72 private boolean selectedForSuppress;
73
74 public DTKitBuilder(TestType[] tests,
75 CoverageType[] coverages,
76 ViolationsType[] violations,
77 MeasureType[] measures,
78 String rootFolderPath,
79 boolean selectedForSuppress
80 ){
81 this.tests = tests;
82 this.coverages = coverages;
83 this.violations = violations;
84 this.measures = measures;
85 this.selectedForSuppress = selectedForSuppress;
86
87 if (rootFolderPath == null || rootFolderPath.trim().isEmpty()){
88 this.rootFolder = DEFAULT_ROOT_PATH;
89 this.generatedFolder = DEFAULT_ROOT_PATH;
90 } else {
91 this.rootFolder = rootFolderPath;
92 if (new File(rootFolderPath).isAbsolute()){
93 this.generatedFolder = DEFAULT_ROOT_PATH;
94 } else {
95 this.generatedFolder = rootFolderPath;
96 }
97 }
98
99 this.generatedTests = generatedFolder + DEFAULT_TEST_PATH;
100 this.generatedCoverage = generatedFolder + DEFAULT_COVERAGE_PATH;
101 this.generatedMeasures = generatedFolder + DEFAULT_MEASURES_PATH;
102 this.generatedViolations = generatedFolder + DEFAULT_VIOLATIONS_PATH;
103 }
104
105 public String getRootFolder() {
106 return rootFolder;
107 }
108
109 public TestType[] getTests() {
110 return tests;
111 }
112
113 public CoverageType[] getCoverages() {
114 return coverages;
115 }
116
117 public ViolationsType[] getViolations() {
118 return violations;
119 }
120
121 public MeasureType[] getMeasures() {
122 return measures;
123 }
124
125 /**
126 * Used with DTKit plugin configuration.
127 * When selectedForSuppress, this option allows to suppress old build files prior to execute a new build
128 */
129 public boolean isSelectedForSuppress() {
130 return selectedForSuppress;
131 }
132
133 public boolean getSelectedForSuppress() {
134 return selectedForSuppress;
135 }
136
137 public void setTests(TestType[] tests) {
138 this.tests = tests;
139 }
140
141 public void setCoverages(CoverageType[] coverages) {
142 this.coverages = coverages;
143 }
144
145 public void setViolations(ViolationsType[] violations) {
146 this.violations = violations;
147 }
148
149 public void setMeasures(MeasureType[] measures) {
150 this.measures = measures;
151 }
152
153 public void setSelectedForSuppress(boolean selectedForSuppress) {
154 this.selectedForSuppress = selectedForSuppress;
155 }
156
157 @Override
158 public BuildStepMonitor getRequiredMonitorService() {
159 return BuildStepMonitor.NONE;
160 }
161
162 @Override
163 public boolean perform(final AbstractBuild<?, ?> build, Launcher launcher, final BuildListener listener)
164 throws InterruptedException, IOException {
165
166 final DTKitBuilderLog log = Guice.createInjector(new AbstractModule() {
167 @Override
168 protected void configure() {
169 bind(BuildListener.class).toInstance(listener);
170 }
171 }).getInstance(DTKitBuilderLog.class);
172
173 try {
174 //Because of old jobs configuration, we have to check that those values are not null
175 String rootFolder = this.rootFolder == null?(DEFAULT_ROOT_PATH):this.rootFolder;
176 String generatedTests = this.generatedTests == null?(rootFolder+DEFAULT_TEST_PATH):this.generatedTests;
177 String generatedCoverage = this.generatedCoverage == null?(rootFolder+DEFAULT_COVERAGE_PATH):this.generatedCoverage;
178 String generatedMeasures = this.generatedMeasures == null?(rootFolder+DEFAULT_MEASURES_PATH):this.generatedMeasures;
179 String generatedViolations = this.generatedViolations == null?(rootFolder+DEFAULT_VIOLATIONS_PATH):this.generatedViolations;
180
181 final StringBuffer sb = new StringBuffer();
182
183 // cause of eventual presence of last build files, need to check for files and to delete them if files exist
184 // check for suppressing old build files
185 if (this.isSelectedForSuppress()) {
186 log.info("Checking for old build files...");
187 this.removeOldBuildFiles(new FilePath(build.getWorkspace(), rootFolder),log);
188 }
189
190 log.info("Starting converting.");
191
192 DTKitReportProcessingService processingService = Guice.createInjector(new AbstractModule() {
193 @Override
194 protected void configure() {
195 bind(BuildListener.class).toInstance(listener);
196 }
197 }).getInstance(DTKitReportProcessingService.class);
198
199
200 boolean isInvoked = false;
201
202 if (new File(rootFolder).isAbsolute()){
203 log.error("Tusar root folder musn't be absolute, result are generated in default folder: " +
204 "[workspace]/generatedDTKITFiles.");
205 }
206
207 // Apply conversion for all tests tools
208 if (tests.length != 0) {
209 FilePath outputFileParent = new FilePath(build.getWorkspace(), generatedTests);
210 outputFileParent.mkdirs();
211 for (TestType testsType : tests) {
212 log.info("Processing " + testsType.getDescriptor().getDisplayName());
213 if (!processingService.isEmptyPattern(testsType.getPattern())) {
214 boolean result = processInputMetricType(build, listener, testsType, outputFileParent);
215 if (result) {
216 isInvoked = true;
217 }
218 }
219 }
220 sb.append(";").append(generatedTests);
221 }
222 if (coverages.length != 0) {
223 FilePath outputFileParent = new FilePath(build.getWorkspace(), generatedCoverage);
224 outputFileParent.mkdirs();
225 for (CoverageType coverageType : coverages) {
226 log.info("Processing " + coverageType.getDescriptor().getDisplayName());
227 if (!processingService.isEmptyPattern(coverageType.getPattern())) {
228 boolean result = processInputMetricType(build, listener, coverageType, outputFileParent);
229 if (result) {
230 isInvoked = true;
231 }
232 }
233 }
234 sb.append(";").append(generatedCoverage);
235 }
236 if (violations.length != 0) {
237 FilePath outputFileParent = new FilePath(build.getWorkspace(), generatedViolations);
238 outputFileParent.mkdirs();
239 for (ViolationsType violationsType : violations) {
240 log.info("Processing " + violationsType.getDescriptor().getDisplayName());
241 if (!processingService.isEmptyPattern(violationsType.getPattern())) {
242 boolean result = processInputMetricType(build, listener, violationsType, outputFileParent);
243 if (result) {
244 isInvoked = true;
245 }
246 }
247 }
248 sb.append(";").append(generatedViolations);
249 }
250 if (measures.length != 0) {
251 FilePath outputFileParent = new FilePath(build.getWorkspace(), generatedMeasures);
252 outputFileParent.mkdirs();
253 for (MeasureType measureType : measures) {
254 log.info("Processing " + measureType.getDescriptor().getDisplayName());
255 if (!processingService.isEmptyPattern(measureType.getPattern())) {
256 boolean result = processInputMetricType(build, listener, measureType, outputFileParent);
257 if (result) {
258 isInvoked = true;
259 }
260 }
261 }
262 sb.append(";").append(generatedMeasures);
263 }
264
265
266 // Remove the first character
267 sb.delete(0, 1);
268
269 constructTUSARReportPathForSonar(build, sb.toString());
270
271 if (!isInvoked) {
272 log.error("Any files are correct. Fail build.");
273 build.setResult(Result.FAILURE);
274 return false;
275 }
276
277 return true;
278 } catch (Throwable e) {
279 e.printStackTrace();
280 log.error("An exception occured. Message :"+e.getMessage()+". Please check the logs for full stack Trace. Fail build.");
281 build.setResult(Result.FAILURE);
282 return false;
283 }
284 }
285 /**
286 * Since it is possible to add several DTKit build steps, it is also possible to have several root folder.
287 * This method will correctly update the sonar.tusar.reportsPaths property when a new dtkit build step is met.
288 * @param build
289 */
290 private void constructTUSARReportPathForSonar(final AbstractBuild<?, ?> build, String reportPaths){
291 boolean isTusarPropertyAlreadyAdded = false;
292
293 ParametersAction a = build.getAction(ParametersAction.class);
294 StringParameterValue parameter = (StringParameterValue) a.getParameter("sonar.tusar.reportsPaths");
295 // If there is more than one dtkit build step, we have to update the
296 // sonar.tusar.reportsPaths property
297 if (null != parameter) {
298 build.removeAction(a);
299 List<ParameterValue> parameterValues = new ArrayList<ParameterValue>();
300 parameterValues.add(new StringParameterValue("sonar.language", "tusar"));
301 parameterValues.add(new StringParameterValue("sonar.tusar.reportsPaths", parameter.getValue() + ";" + reportPaths));
302 build.addAction(new ParametersAction(parameterValues));
303 isTusarPropertyAlreadyAdded = true;
304 }
305
306 //The first dtkit met build step is treated here
307 if(false == isTusarPropertyAlreadyAdded) {
308 List<ParameterValue> parameterValues = new ArrayList<ParameterValue>();
309 parameterValues.add(new StringParameterValue("sonar.language", "tusar"));
310 parameterValues.add(new StringParameterValue("sonar.tusar.reportsPaths", reportPaths));
311 build.addAction(new ParametersAction(parameterValues));
312 }
313 }
314
315 private boolean processInputMetricType(final AbstractBuild<?, ?> build, final BuildListener listener, MetricsType metricsType, FilePath outputFileParent) throws IOException, InterruptedException {
316
317 //Retrieves the pattern
318 String newExpandedPattern = metricsType.getPattern();
319 newExpandedPattern = newExpandedPattern.replaceAll("[\t\r\n]+", " ");
320 newExpandedPattern = Util.replaceMacro(newExpandedPattern, build.getEnvironment(listener));
321
322 //Build a new build info
323 final DTKitBuilderToolInfo toolInfo = new DTKitBuilderToolInfo(metricsType, new File(outputFileParent.toURI()), newExpandedPattern, build.getTimeInMillis());
324
325 // Archiving tool reports into JUnit files
326 DTKitBuilderTransformer dtkitBuilderTransformer = Guice.createInjector(new AbstractModule() {
327 @Override
328 protected void configure() {
329 bind(BuildListener.class).toInstance(listener);
330 bind(DTKitBuilderToolInfo.class).toInstance(toolInfo);
331 bind(DTKitBuilderValidationService.class).in(Singleton.class);
332 bind(DTKitBuilderConversionService.class).in(Singleton.class);
333 bind(DTKitBuilderLog.class).in(Singleton.class);
334 bind(DTKitReportProcessingService.class).in(Singleton.class);
335 }
336 }).getInstance(DTKitBuilderTransformer.class);
337
338 return build.getWorkspace().act(dtkitBuilderTransformer);
339 }
340
341
342 /**
343 * This method performs an iterative walk into output directory tree to delete all old build files if present
344 * @param rootFile the root folder that contains all output sub-folders and files
345 * @param log
346 * @throws IOException
347 * @throws InterruptedException
348 */
349 private void removeOldBuildFiles(FilePath rootFile, DTKitBuilderLog log) throws IOException, InterruptedException {
350 if (rootFile ==null){
351 log.warning("The given root folder is null, the clean operation is cancelled");
352 return;
353 }
354 List<FilePath> dirList = rootFile.list(); //return null if the rootFile does not exist
355 if (dirList ==null){
356 log.warning("The given root folder does not exist, the clean operation is cancelled");
357 return;
358 }
359 ListIterator<FilePath> dirListIterator = dirList.listIterator();
360 List<FilePath> fileList;
361 ListIterator<FilePath> fileListIterator;
362 FilePath tempDir, tempFile;
363
364 // check for output folders
365 if (!dirList.isEmpty()) {
366 while (dirListIterator.hasNext()) {
367 tempDir = dirListIterator.next();
368 fileList = tempDir.list();
369 // check for output files into folder
370 if ((!fileList.isEmpty())) {
371 fileListIterator = fileList.listIterator();
372 while (fileListIterator.hasNext()) {
373 tempFile = fileListIterator.next();
374 if (tempFile.exists()) {
375 // and delete them if present
376 tempFile.delete();
377 }
378 }
379 }
380 }
381 }
382 }
383
384 @Extension
385 public static final class TusarNotifierDescriptor extends BuildStepDescriptor<Builder> {
386
387
388 public TusarNotifierDescriptor() {
389 super(DTKitBuilder.class);
390 load();
391 }
392
393 @SuppressWarnings("rawtypes")
394 @Override
395 public boolean isApplicable(Class<? extends AbstractProject> jobType) {
396 return true;
397 }
398
399 @Override
400 public String getHelpFile() {
401 return "/plugin/dtkit/help.html";
402 }
403
404
405 @Override
406 public String getDisplayName() {
407 return "DTKit Conversion";
408 }
409
410 public DescriptorExtensionList<TestType, TestTypeDescriptor<?>> getListTestDescriptors() {
411 return TestTypeDescriptor.all();
412 }
413
414 public DescriptorExtensionList<ViolationsType, ViolationsTypeDescriptor<?>> getListViolationDescriptors() {
415 return ViolationsTypeDescriptor.all();
416 }
417
418 public DescriptorExtensionList<MeasureType, MeasureTypeDescriptor<?>> getListMeasureDescriptors() {
419 return MeasureTypeDescriptor.all();
420 }
421
422 public DescriptorExtensionList<CoverageType, CoverageTypeDescriptor<?>> getListCoverageDescriptors() {
423 return CoverageTypeDescriptor.all();
424 }
425
426 @Override
427 public Builder newInstance(StaplerRequest req, JSONObject formData)
428 throws FormException {
429
430 List<TestType> tests = Descriptor.newInstancesFromHeteroList(req, formData, "tests", getListTestDescriptors());
431 List<CoverageType> coverages = Descriptor.newInstancesFromHeteroList(req, formData, "coverages", getListCoverageDescriptors());
432 List<ViolationsType> violations = Descriptor.newInstancesFromHeteroList(req, formData, "violations", getListViolationDescriptors());
433 List<MeasureType> measures = Descriptor.newInstancesFromHeteroList(req, formData, "measures", getListMeasureDescriptors());
434
435 return new DTKitBuilder(tests.toArray(new TestType[tests.size()]),
436 coverages.toArray(new CoverageType[coverages.size()]),
437 violations.toArray(new ViolationsType[violations.size()]),
438 measures.toArray(new MeasureType[measures.size()]),
439 formData.getString("rootFolder"),
440 formData.getBoolean("selectedForSuppress")
441 );
442 }
443 }
444
445}