001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * SonarQube is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.batch.maven;
021
022 import com.google.common.annotations.VisibleForTesting;
023 import com.google.common.base.Function;
024 import com.google.common.base.Predicate;
025 import com.google.common.collect.Collections2;
026 import com.google.common.collect.Lists;
027 import com.google.common.collect.Maps;
028 import org.apache.commons.lang.StringUtils;
029 import org.apache.maven.model.CiManagement;
030 import org.apache.maven.model.IssueManagement;
031 import org.apache.maven.model.Scm;
032 import org.apache.maven.project.MavenProject;
033 import org.sonar.api.CoreProperties;
034 import org.sonar.api.batch.SupportedEnvironment;
035 import org.sonar.api.batch.bootstrap.ProjectDefinition;
036 import org.sonar.api.batch.maven.MavenUtils;
037 import org.sonar.api.task.TaskExtension;
038 import org.sonar.api.utils.MessageException;
039 import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
040 import org.sonar.java.api.JavaUtils;
041
042 import javax.annotation.Nullable;
043
044 import java.io.File;
045 import java.io.IOException;
046 import java.util.Arrays;
047 import java.util.Collection;
048 import java.util.List;
049 import java.util.Map;
050
051 /**
052 * @deprecated since 4.3 kept only to support old version of SonarQube Mojo
053 */
054 @Deprecated
055 @SupportedEnvironment("maven")
056 public class MavenProjectConverter implements TaskExtension {
057
058 private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." +
059 " Probably you use Maven Advanced Reactor Options, which is not supported by SonarQube and should not be used.";
060
061 public ProjectDefinition configure(List<MavenProject> poms, MavenProject root) {
062 // projects by canonical path to pom.xml
063 Map<String, MavenProject> paths = Maps.newHashMap();
064 Map<MavenProject, ProjectDefinition> defs = Maps.newHashMap();
065
066 try {
067 configureModules(poms, paths, defs);
068
069 rebuildModuleHierarchy(paths, defs);
070 } catch (IOException e) {
071 throw new IllegalStateException("Cannot configure project", e);
072 }
073
074 ProjectDefinition rootProject = defs.get(root);
075 if (rootProject == null) {
076 throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
077 }
078 return rootProject;
079 }
080
081 private void rebuildModuleHierarchy(Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
082 for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
083 MavenProject pom = entry.getValue();
084 for (Object m : pom.getModules()) {
085 String moduleId = (String) m;
086 File modulePath = new File(pom.getBasedir(), moduleId);
087 MavenProject module = findMavenProject(modulePath, paths);
088
089 ProjectDefinition parentProject = defs.get(pom);
090 if (parentProject == null) {
091 throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
092 }
093 ProjectDefinition subProject = defs.get(module);
094 if (subProject == null) {
095 throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
096 }
097 parentProject.addSubProject(subProject);
098 }
099 }
100 }
101
102 private void configureModules(List<MavenProject> poms, Map<String, MavenProject> paths, Map<MavenProject, ProjectDefinition> defs) throws IOException {
103 for (MavenProject pom : poms) {
104 paths.put(pom.getFile().getCanonicalPath(), pom);
105 ProjectDefinition def = ProjectDefinition.create();
106 merge(pom, def);
107 defs.put(pom, def);
108 }
109 }
110
111 private static MavenProject findMavenProject(final File modulePath, Map<String, MavenProject> paths) throws IOException {
112 if (modulePath.exists() && modulePath.isDirectory()) {
113 for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
114 String pomFileParentDir = new File(entry.getKey()).getParent();
115 if (pomFileParentDir.equals(modulePath.getCanonicalPath())) {
116 return entry.getValue();
117 }
118 }
119 return null;
120 }
121 return paths.get(modulePath.getCanonicalPath());
122 }
123
124 @VisibleForTesting
125 void merge(MavenProject pom, ProjectDefinition definition) {
126 String key = getSonarKey(pom);
127 // IMPORTANT NOTE : reference on properties from POM model must not be saved,
128 // instead they should be copied explicitly - see SONAR-2896
129 definition
130 .setProperties(pom.getModel().getProperties())
131 .setKey(key)
132 .setVersion(pom.getVersion())
133 .setName(pom.getName())
134 .setDescription(pom.getDescription())
135 .addContainerExtension(pom);
136 guessJavaVersion(pom, definition);
137 guessEncoding(pom, definition);
138 convertMavenLinksToProperties(definition, pom);
139 synchronizeFileSystem(pom, definition);
140 }
141
142 private static String getSonarKey(MavenProject pom) {
143 return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString();
144 }
145
146 private static void guessEncoding(MavenProject pom, ProjectDefinition definition) {
147 // See http://jira.codehaus.org/browse/SONAR-2151
148 String encoding = MavenUtils.getSourceEncoding(pom);
149 if (encoding != null) {
150 definition.setProperty(CoreProperties.ENCODING_PROPERTY, encoding);
151 }
152 }
153
154 private static void guessJavaVersion(MavenProject pom, ProjectDefinition definition) {
155 // See http://jira.codehaus.org/browse/SONAR-2148
156 // Get Java source and target versions from maven-compiler-plugin.
157 String version = MavenUtils.getJavaSourceVersion(pom);
158 if (version != null) {
159 definition.setProperty(JavaUtils.JAVA_SOURCE_PROPERTY, version);
160 }
161 version = MavenUtils.getJavaVersion(pom);
162 if (version != null) {
163 definition.setProperty(JavaUtils.JAVA_TARGET_PROPERTY, version);
164 }
165 }
166
167 /**
168 * For SONAR-3676
169 */
170 private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) {
171 setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl());
172
173 Scm scm = pom.getScm();
174 if (scm == null) {
175 scm = new Scm();
176 }
177 setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl());
178 setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection());
179
180 CiManagement ci = pom.getCiManagement();
181 if (ci == null) {
182 ci = new CiManagement();
183 }
184 setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl());
185
186 IssueManagement issues = pom.getIssueManagement();
187 if (issues == null) {
188 issues = new IssueManagement();
189 }
190 setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl());
191 }
192
193 private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) {
194 if (StringUtils.isBlank(definition.getProperties().getProperty(propertyKey))) {
195 definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue));
196 }
197 }
198
199 public void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) {
200 into.setBaseDir(pom.getBasedir());
201 File buildDir = getBuildDir(pom);
202 if (buildDir != null) {
203 into.setBuildDir(buildDir);
204 into.setWorkDir(getSonarWorkDir(pom));
205 }
206 into.setSourceDirs(toPaths(mainDirs(pom)));
207 into.setTestDirs(toPaths(testDirs(pom)));
208 File binaryDir = resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir());
209 if (binaryDir != null) {
210 into.addBinaryDir(binaryDir);
211 }
212 }
213
214 public static File getSonarWorkDir(MavenProject pom) {
215 return new File(getBuildDir(pom), "sonar");
216 }
217
218 private static File getBuildDir(MavenProject pom) {
219 return resolvePath(pom.getBuild().getDirectory(), pom.getBasedir());
220 }
221
222 public void synchronizeFileSystem(MavenProject pom, DefaultModuleFileSystem into) {
223 into.resetDirs(
224 pom.getBasedir(),
225 getBuildDir(pom),
226 mainDirs(pom),
227 testDirs(pom),
228 Arrays.asList(resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir())));
229 }
230
231 static File resolvePath(@Nullable String path, File basedir) {
232 if (path != null) {
233 File file = new File(StringUtils.trim(path));
234 if (!file.isAbsolute()) {
235 try {
236 file = new File(basedir, path).getCanonicalFile();
237 } catch (IOException e) {
238 throw new IllegalStateException("Unable to resolve path '" + path + "'", e);
239 }
240 }
241 return file;
242 }
243 return null;
244 }
245
246 static List<File> resolvePaths(List<String> paths, File basedir) {
247 List<File> result = Lists.newArrayList();
248 for (String path : paths) {
249 File dir = resolvePath(path, basedir);
250 if (dir != null) {
251 result.add(dir);
252 }
253 }
254 return result;
255 }
256
257 private List<File> mainDirs(MavenProject pom) {
258 return sourceDirs(pom, ProjectDefinition.SOURCE_DIRS_PROPERTY, pom.getCompileSourceRoots());
259 }
260
261 private List<File> testDirs(MavenProject pom) {
262 return sourceDirs(pom, ProjectDefinition.TEST_DIRS_PROPERTY, pom.getTestCompileSourceRoots());
263 }
264
265 private List<File> sourceDirs(MavenProject pom, String propertyKey, List mavenDirs) {
266 List<String> paths;
267 String prop = pom.getProperties().getProperty(propertyKey);
268 if (prop != null) {
269 paths = Arrays.asList(StringUtils.split(prop, ","));
270 // do not remove dirs that do not exist. They must be kept in order to
271 // notify users that value of sonar.sources has a typo.
272 return existingDirsOrFail(resolvePaths(paths, pom.getBasedir()), pom, propertyKey);
273 }
274
275 List<File> dirs = resolvePaths(mavenDirs, pom.getBasedir());
276
277 // Maven provides some directories that do not exist. They
278 // should be removed
279 return keepExistingDirs(dirs);
280 }
281
282 private List<File> existingDirsOrFail(List<File> dirs, MavenProject pom, String propertyKey) {
283 for (File dir : dirs) {
284 if (!dir.isDirectory() || !dir.exists()) {
285 throw MessageException.of(String.format(
286 "The directory '%s' does not exist for Maven module %s. Please check the property %s",
287 dir.getAbsolutePath(), pom.getId(), propertyKey));
288 }
289 }
290 return dirs;
291 }
292
293 private static List<File> keepExistingDirs(List<File> files) {
294 return Lists.newArrayList(Collections2.filter(files, new Predicate<File>() {
295 @Override
296 public boolean apply(File dir) {
297 return dir != null && dir.exists() && dir.isDirectory();
298 }
299 }));
300 }
301
302 private static String[] toPaths(Collection<File> dirs) {
303 Collection<String> paths = Collections2.transform(dirs, new Function<File, String>() {
304 @Override
305 public String apply(File dir) {
306 return dir.getAbsolutePath();
307 }
308 });
309 return paths.toArray(new String[paths.size()]);
310 }
311 }