1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.whitesource.teamcity.agent;
17
18 import jetbrains.buildServer.agent.AgentRunningBuild;
19 import jetbrains.buildServer.agent.BuildRunnerContext;
20 import jetbrains.buildServer.log.Loggers;
21 import jetbrains.buildServer.util.FileUtil;
22 import jetbrains.buildServer.util.StringUtil;
23 import org.jdom.Element;
24 import org.jdom.JDOMException;
25 import org.jdom.output.Format;
26 import org.jdom.output.XMLOutputter;
27 import org.whitesource.agent.api.ChecksumUtils;
28 import org.whitesource.agent.api.model.AgentProjectInfo;
29 import org.whitesource.agent.api.model.Coordinates;
30 import org.whitesource.agent.api.model.DependencyInfo;
31 import org.whitesource.teamcity.common.Constants;
32 import org.whitesource.teamcity.common.WssUtils;
33
34 import java.io.File;
35 import java.io.IOException;
36 import java.util.*;
37
38
39
40
41
42
43
44 public class MavenOssInfoExtractor extends BaseOssInfoExtractor {
45
46
47
48 private static final String MAVEN_BUILD_INFO_XML = "maven-build-info.xml";
49
50 private static final String LOG_COMPONENT = "MavenExtractor";
51
52
53
54 protected Map<String, String> moduleTokens;
55
56 protected boolean ignorePomModules;
57
58
59
60
61
62
63
64
65 public MavenOssInfoExtractor(BuildRunnerContext runner) {
66 super(runner);
67
68 ignorePomModules = Boolean.parseBoolean(
69 runner.getRunnerParameters().get(Constants.RUNNER_IGNORE_POM_MODULES));
70 moduleTokens = WssUtils.splitParametersMap(
71 runner.getRunnerParameters().get(Constants.RUNNER_MODULE_TOKENS));
72
73 }
74
75
76
77 @Override
78 public Collection<AgentProjectInfo> extract() {
79 Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, "Collection started"));
80
81 Collection<AgentProjectInfo> projectInfos = new ArrayList<AgentProjectInfo>();
82
83 final AgentRunningBuild build = runner.getBuild();
84
85
86 File mavenBuildInfoFile = new File(build.getBuildTempDirectory(), MAVEN_BUILD_INFO_XML);
87 if (!mavenBuildInfoFile.exists()) {
88 String missingMavenInfo = "Can't find maven build info report.";
89 Loggers.AGENT.warn(WssUtils.logMsg(LOG_COMPONENT, missingMavenInfo));
90 build.getBuildLogger().warning(missingMavenInfo);
91 throw new RuntimeException("Error collecting maven information, Skipping update.");
92 }
93
94
95 try {
96 Element root = FileUtil.parseDocument(mavenBuildInfoFile);
97
98 XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
99 Loggers.AGENT.info(WssUtils.logMsg(LOG_COMPONENT, xmlOutputter.outputString(root)));
100
101 Map<String, Coordinates> hierarchy = new HashMap<String, Coordinates>();
102 processHierarchy(root.getChild("hierarchy"), hierarchy);
103
104 Element projects = root.getChild("projects");
105 if (projects != null) {
106 List<Element> projectList = projects.getChildren("project");
107 for (Element projectElement : projectList) {
108 if (!shouldProcess(projectElement)) {
109 continue;
110 }
111
112 String groupId = projectElement.getChildText("groupId");
113 String artifactId = projectElement.getChildText("artifactId");
114 String version = projectElement.getChildText("version");
115
116 build.getBuildLogger().message("Processing " + artifactId);
117
118 AgentProjectInfo projectInfo = new AgentProjectInfo();
119 projectInfo.setCoordinates(new Coordinates(groupId, artifactId, version));
120 projectInfo.setParentCoordinates(hierarchy.get(projectElement.getChildText("id")));
121
122 if (projectList.size() == 1) {
123 projectInfo.setProjectToken(projectToken);
124 } else {
125 projectInfo.setProjectToken(moduleTokens.get(artifactId));
126 }
127
128 Element dependencyArtifacts = projectElement.getChild("dependencyArtifacts");
129 if (dependencyArtifacts != null) {
130 List<Element> dependencyList = dependencyArtifacts.getChildren("artifact");
131 for (Element dependencyElement : dependencyList) {
132 if (isDirectDependency(dependencyElement)) {
133 DependencyInfo info = new DependencyInfo();
134
135 info.setGroupId(dependencyElement.getChildText("groupId"));
136 info.setArtifactId(dependencyElement.getChildText("artifactId"));
137 info.setVersion(dependencyElement.getChildText("version"));
138 info.setClassifier(dependencyElement.getChildText("classifier"));
139 info.setType(dependencyElement.getChildText("type"));
140 info.setScope(dependencyElement.getChildText("scope"));
141
142 String dependencyPath = dependencyElement.getChildText("path");
143 if (!StringUtil.isEmptyOrSpaces(dependencyPath)) {
144 info.setSystemPath(dependencyPath);
145 try {
146 info.setSha1(ChecksumUtils.calculateSHA1(new File(dependencyPath)));
147 } catch (IOException e) {
148 Loggers.AGENT.debug("Unable to calculate SHA-1 for " + dependencyPath);
149 }
150 }
151
152 projectInfo.getDependencies().add(info);
153 }
154 }
155 }
156
157 build.getBuildLogger().message("Found " + projectInfo.getDependencies().size() + " direct dependencies");
158 projectInfos.add(projectInfo);
159 }
160 }
161 } catch (JDOMException e) {
162 throw new RuntimeException("Error parsing maven information.", e);
163 } catch (IOException e) {
164 throw new RuntimeException("Error reading maven information.", e);
165 }
166
167 return projectInfos;
168 }
169
170
171
172
173 private void processHierarchy(Element root, Map<String, Coordinates> hierarchy) {
174 if (root == null) {
175 return;
176 }
177
178 List<Element> nodes = root.getChildren("node");
179 for(Element node : nodes) {
180 Coordinates parentCoordinates= idToCoordinates(node.getChildText("id"));
181 Element children = node.getChild("children");
182 List<Element> childrenNodes = children.getChildren("node");
183 for (Element child : childrenNodes) {
184 hierarchy.put(child.getChildText("id"), parentCoordinates);
185 processHierarchy(child, hierarchy);
186 }
187 }
188 }
189
190 private Coordinates idToCoordinates(String id) {
191 Coordinates coordinates = null;
192
193 String[] split = id.split(":");
194 if (split.length > 3) {
195 coordinates = new Coordinates(split[0], split[1], split[3]);
196 }
197
198 return coordinates;
199 }
200
201 private boolean isDirectDependency(Element dependencyElement) {
202
203
204 return dependencyElement.getChild("dependencyTrail").getChildren("id").size() == 2;
205 }
206
207 private boolean shouldProcess(Element project) {
208 boolean process = true;
209
210 String artifactId = project.getChildText("artifactId");
211 String packaging = project.getChildText("packaging");
212
213 if (ignorePomModules && "pom".equals(packaging)) {
214 process = false;
215 } else if (!excludes.isEmpty() && matchAny(artifactId, excludes)) {
216 process = false;
217 } else if (!includes.isEmpty() && matchAny(artifactId, includes)) {
218 process = true;
219 }
220
221 return process;
222 }
223
224 private boolean matchAny(String value, List<String> patterns) {
225 boolean match = false;
226
227 for (String pattern : patterns) {
228 String regex = pattern.replace(".", "\\.").replace("*", ".*");
229 if (value.matches(regex)) {
230 match = true;
231 break;
232 }
233 }
234
235 return match;
236 }
237 }