View Javadoc

1   /**
2    * Copyright (C) 2012 White Source Ltd.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * Concrete implementation for maven job type.
40   * Based on teamcity's report for maven projects.
41   *
42   * @author Edo.Shor
43   */
44  public class MavenOssInfoExtractor extends BaseOssInfoExtractor {
45  
46      /* --- Static members --- */
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      /* --- Members --- */
53  
54      protected Map<String, String> moduleTokens;
55  
56      protected boolean ignorePomModules;
57  
58      /* --- Constructors --- */
59  
60      /**
61       * Constructor
62       *
63       * @param runner
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      /* --- Concrete implementation methods --- */
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          // find and maven build info report
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          // parse maven info report and extract OSS usage information
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     /* --- Private methods --- */
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         // check the dependency trail to see only two dependents.
203         // Expecting actual module and self for direct dependencies.
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 }