001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar 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     * Sonar 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
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.batch;
021    
022    import com.google.common.annotations.VisibleForTesting;
023    import com.google.common.collect.Maps;
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.maven.model.CiManagement;
026    import org.apache.maven.model.IssueManagement;
027    import org.apache.maven.model.Scm;
028    import org.apache.maven.project.MavenProject;
029    import org.sonar.api.CoreProperties;
030    import org.sonar.api.batch.bootstrap.ProjectDefinition;
031    import org.sonar.api.utils.SonarException;
032    
033    import java.io.File;
034    import java.io.IOException;
035    import java.util.List;
036    import java.util.Map;
037    
038    public final class MavenProjectConverter {
039    
040      private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." +
041        " Probably you use Maven Advanced Reactor Options, which is not supported by Sonar and should not be used.";
042    
043      public static ProjectDefinition convert(List<MavenProject> poms, MavenProject root) {
044        Map<String, MavenProject> paths = Maps.newHashMap(); // projects by canonical path to pom.xml
045        Map<MavenProject, ProjectDefinition> defs = Maps.newHashMap();
046    
047        try {
048          for (MavenProject pom : poms) {
049            paths.put(pom.getFile().getCanonicalPath(), pom);
050            defs.put(pom, convert(pom));
051          }
052    
053          for (Map.Entry<String, MavenProject> entry : paths.entrySet()) {
054            MavenProject pom = entry.getValue();
055            for (Object m : pom.getModules()) {
056              String moduleId = (String) m;
057              File modulePath = new File(pom.getBasedir(), moduleId);
058              if (modulePath.exists() && modulePath.isDirectory()) {
059                modulePath = new File(modulePath, "pom.xml");
060              }
061              MavenProject module = paths.get(modulePath.getCanonicalPath());
062    
063              ProjectDefinition parentProject = defs.get(pom);
064              ProjectDefinition subProject = defs.get(module);
065              if (parentProject == null) {
066                throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
067              }
068              if (subProject == null) {
069                throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
070              }
071              parentProject.addSubProject(subProject);
072            }
073          }
074        } catch (IOException e) {
075          throw new SonarException(e);
076        }
077    
078        ProjectDefinition rootProject = defs.get(root);
079        if (rootProject == null) {
080          throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE);
081        }
082    
083        return rootProject;
084      }
085    
086      @VisibleForTesting
087      static ProjectDefinition convert(MavenProject pom) {
088        String key = new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString();
089        ProjectDefinition definition = ProjectDefinition.create();
090        // IMPORTANT NOTE : reference on properties from POM model must not be saved, instead they should be copied explicitly - see SONAR-2896
091        definition
092            .setProperties(pom.getModel().getProperties())
093            .setKey(key)
094            .setVersion(pom.getVersion())
095            .setName(pom.getName())
096            .setDescription(pom.getDescription())
097            .addContainerExtension(pom);
098    
099        convertMavenLinksToProperties(definition, pom);
100    
101        synchronizeFileSystem(pom, definition);
102    
103        return definition;
104      }
105    
106      /**
107       * For SONAR-3676
108       */
109      private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) {
110        setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl());
111    
112        Scm scm = pom.getScm();
113        if (scm == null) {
114          scm = new Scm();
115        }
116        setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl());
117        setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection());
118    
119        CiManagement ci = pom.getCiManagement();
120        if (ci == null) {
121          ci = new CiManagement();
122        }
123        setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl());
124    
125        IssueManagement issues = pom.getIssueManagement();
126        if (issues == null) {
127          issues = new IssueManagement();
128        }
129        setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl());
130      }
131    
132      private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) {
133        if (StringUtils.isBlank(definition.getProperties().getProperty(propertyKey))) {
134          definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue));
135        }
136      }
137    
138      public static void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) {
139        into.setBaseDir(pom.getBasedir());
140        into.setWorkDir(new File(resolvePath(pom.getBuild().getDirectory(), pom.getBasedir()), "sonar"));
141        into.setSourceDirs((String[]) pom.getCompileSourceRoots().toArray(new String[pom.getCompileSourceRoots().size()]));
142        into.setTestDirs((String[]) pom.getTestCompileSourceRoots().toArray(new String[pom.getTestCompileSourceRoots().size()]));
143      }
144    
145      static File resolvePath(String path, File basedir) {
146        File file = new File(path);
147        if (!file.isAbsolute()) {
148          try {
149            file = new File(basedir, path).getCanonicalFile();
150          } catch (IOException e) {
151            throw new SonarException("Unable to resolve path '" + path + "'", e);
152          }
153        }
154        return file;
155      }
156    
157      private MavenProjectConverter() {
158        // only static methods
159      }
160    }