/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.scanner.report; import java.util.Collection; import java.util.stream.Collectors; import javax.annotation.CheckForNull; import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.InputComponent; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputModule; import org.sonar.api.batch.fs.InputPath; import org.sonar.api.batch.fs.internal.DefaultInputComponent; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.batch.fs.internal.InputComponentTree; import org.sonar.api.batch.fs.internal.InputModuleHierarchy; import org.sonar.core.util.CloseableIterator; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportReader; import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType; import org.sonar.scanner.protocol.output.ScannerReport.ComponentLink; import org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType; import org.sonar.scanner.protocol.output.ScannerReport.Issue; import org.sonar.scanner.protocol.output.ScannerReportWriter; /** * Adds components and analysis metadata to output report */ public class ComponentsPublisher implements ReportPublisherStep { private InputComponentTree componentTree; private InputModuleHierarchy moduleHierarchy; private ScannerReportReader reader; private ScannerReportWriter writer; public ComponentsPublisher(InputModuleHierarchy moduleHierarchy, InputComponentTree inputComponentTree) { this.moduleHierarchy = moduleHierarchy; this.componentTree = inputComponentTree; } @Override public void publish(ScannerReportWriter writer) { this.reader = new ScannerReportReader(writer.getFileStructure().root()); this.writer = writer; recursiveWriteComponent((DefaultInputComponent) moduleHierarchy.root()); } /** * Writes the tree of components recursively, deep-first. * @return true if component was written (not skipped) */ private boolean recursiveWriteComponent(DefaultInputComponent component) { Collection<InputComponent> children = componentTree.getChildren(component).stream() .filter(c -> recursiveWriteComponent((DefaultInputComponent) c)) .collect(Collectors.toList()); if (shouldSkipComponent(component, children)) { return false; } ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder(); // non-null fields builder.setRef(component.batchId()); builder.setType(getType(component)); // Don't set key on directories and files to save space since it can be deduced from path if (component instanceof InputModule) { DefaultInputModule inputModule = (DefaultInputModule) component; // Here we want key without branch builder.setKey(inputModule.key()); // protocol buffers does not accept null values String name = getName(inputModule); if (name != null) { builder.setName(name); } String description = getDescription(inputModule); if (description != null) { builder.setDescription(description); } writeVersion(inputModule, builder); } if (component.isFile()) { DefaultInputFile file = (DefaultInputFile) component; builder.setIsTest(file.type() == InputFile.Type.TEST); builder.setLines(file.lines()); String lang = getLanguageKey(file); if (lang != null) { builder.setLanguage(lang); } } String path = getPath(component); if (path != null) { builder.setPath(path); } for (InputComponent child : children) { builder.addChildRef(((DefaultInputComponent) child).batchId()); } writeLinks(component, builder); writer.writeComponent(builder.build()); return true; } private boolean shouldSkipComponent(DefaultInputComponent component, Collection<InputComponent> children) { if (component instanceof InputDir && children.isEmpty()) { try (CloseableIterator<Issue> componentIssuesIt = reader.readComponentIssues(component.batchId())) { if (!componentIssuesIt.hasNext()) { // no file to publish on a directory without issues -> skip it return true; } } } else if (component instanceof DefaultInputFile) { // skip files not marked for publishing DefaultInputFile inputFile = (DefaultInputFile) component; return !inputFile.publish(); } return false; } private static void writeVersion(DefaultInputModule module, ScannerReport.Component.Builder builder) { ProjectDefinition def = module.definition(); String version = getVersion(def); if (version != null) { builder.setVersion(version); } } @CheckForNull private String getPath(InputComponent component) { if (component instanceof InputPath) { InputPath inputPath = (InputPath) component; if (StringUtils.isEmpty(inputPath.relativePath())) { return "/"; } else { return inputPath.relativePath(); } } else if (component instanceof InputModule) { InputModule module = (InputModule) component; return moduleHierarchy.relativePath(module); } throw new IllegalStateException("Unkown component: " + component.getClass()); } private static String getVersion(ProjectDefinition def) { String version = def.getOriginalVersion(); if (StringUtils.isNotBlank(version)) { return version; } return def.getParent() != null ? getVersion(def.getParent()) : null; } private static void writeLinks(InputComponent c, ScannerReport.Component.Builder builder) { if (c instanceof InputModule) { DefaultInputModule inputModule = (DefaultInputModule) c; ProjectDefinition def = inputModule.definition(); ComponentLink.Builder linkBuilder = ComponentLink.newBuilder(); writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_HOME_PAGE, ComponentLinkType.HOME); writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_CI, ComponentLinkType.CI); writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_ISSUE_TRACKER, ComponentLinkType.ISSUE); writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_SOURCES, ComponentLinkType.SCM); writeProjectLink(builder, def, linkBuilder, CoreProperties.LINKS_SOURCES_DEV, ComponentLinkType.SCM_DEV); } } private static void writeProjectLink(ScannerReport.Component.Builder componentBuilder, ProjectDefinition def, ComponentLink.Builder linkBuilder, String linkProp, ComponentLinkType linkType) { String link = def.properties().get(linkProp); if (StringUtils.isNotBlank(link)) { linkBuilder.setType(linkType); linkBuilder.setHref(link); componentBuilder.addLink(linkBuilder.build()); linkBuilder.clear(); } } @CheckForNull private static String getLanguageKey(InputFile file) { return file.language(); } @CheckForNull private static String getName(DefaultInputModule module) { if (StringUtils.isNotEmpty(module.definition().getBranch())) { return module.definition().getOriginalName() + " " + module.definition().getBranch(); } else { return module.definition().getOriginalName(); } } @CheckForNull private static String getDescription(DefaultInputModule module) { return module.definition().getDescription(); } private ComponentType getType(InputComponent r) { if (r instanceof InputFile) { return ComponentType.FILE; } else if (r instanceof InputDir) { return ComponentType.DIRECTORY; } else if ((r instanceof InputModule) && moduleHierarchy.isRoot((InputModule) r)) { return ComponentType.PROJECT; } else if (r instanceof InputModule) { return ComponentType.MODULE; } throw new IllegalArgumentException("Unknown resource type: " + r); } }