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.scan;
021    
022    import com.google.common.base.Joiner;
023    import org.apache.commons.lang.StringUtils;
024    import org.sonar.api.CoreProperties;
025    import org.sonar.api.batch.bootstrap.ProjectDefinition;
026    import org.sonar.api.batch.bootstrap.ProjectReactor;
027    import org.sonar.api.config.Settings;
028    import org.sonar.api.resources.Qualifiers;
029    import org.sonar.api.utils.SonarException;
030    import org.sonar.core.component.ComponentKeys;
031    import org.sonar.core.resource.ResourceDao;
032    import org.sonar.core.resource.ResourceDto;
033    
034    import javax.annotation.Nullable;
035    
036    import java.util.ArrayList;
037    import java.util.List;
038    
039    /**
040     * This class aims at validating project reactor
041     * @since 3.6
042     */
043    public class ProjectReactorValidator {
044    
045      private static final String SONAR_PHASE = "sonar.phase";
046      private final Settings settings;
047      private final ResourceDao resourceDao;
048    
049      public ProjectReactorValidator(Settings settings, ResourceDao resourceDao) {
050        this.settings = settings;
051        this.resourceDao = resourceDao;
052      }
053    
054      public void validate(ProjectReactor reactor) {
055        preventAutomaticProjectCreationIfNeeded(reactor);
056    
057        String branch = settings.getString(CoreProperties.PROJECT_BRANCH_PROPERTY);
058        String rootProjectKey = ComponentKeys.createKey(reactor.getRoot().getKey(), branch);
059    
060        List<String> validationMessages = new ArrayList<String>();
061        checkDeprecatedProperties(validationMessages);
062    
063        for (ProjectDefinition moduleDef : reactor.getProjects()) {
064          validateModule(moduleDef, validationMessages, branch, rootProjectKey);
065        }
066    
067        validateBranch(validationMessages, branch);
068    
069        if (!validationMessages.isEmpty()) {
070          throw new SonarException("Validation of project reactor failed:\n  o " + Joiner.on("\n  o ").join(validationMessages));
071        }
072      }
073    
074      private void preventAutomaticProjectCreationIfNeeded(ProjectReactor reactor) {
075        if (settings.getBoolean(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION)) {
076          String projectKey = reactor.getRoot().getKeyWithBranch();
077          if (resourceDao.findByKey(projectKey) == null) {
078            throw new SonarException(String.format("Unable to scan non-existing project \"%s\"", projectKey));
079          }
080        }
081      }
082    
083      private void validateModule(ProjectDefinition moduleDef, List<String> validationMessages, @Nullable String branch, String rootProjectKey) {
084        if (!ComponentKeys.isValidModuleKey(moduleDef.getKey())) {
085          validationMessages.add(String.format("\"%s\" is not a valid project or module key. "
086            + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", moduleDef.getKey()));
087        } else if (isSubProject(moduleDef)) {
088          // SONAR-4692 Validate root project is the same than previous analysis to avoid module with same key in different projects
089          String moduleKey = ComponentKeys.createKey(moduleDef.getKey(), branch);
090          ResourceDto rootInDB = resourceDao.getRootProjectByComponentKey(moduleKey);
091          if (rootInDB == null || Qualifiers.LIBRARY.equals(rootInDB.getQualifier())) {
092            // This is a new module or previously a library so OK
093            return;
094          }
095          if (rootInDB.getKey().equals(moduleKey)) {
096            // SONAR-4245 current subproject is actually a root project in SQ DB
097            throw new SonarException(
098              String.format("The project '%s' is already defined in SonarQube but not as a module of project '%s'. "
099                + "If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.",
100                moduleKey, rootProjectKey, moduleKey, rootProjectKey));
101          }
102          if (!rootProjectKey.equals(rootInDB.getKey())) {
103            // SONAR-4692 current subproject is already a subproject in another project
104            throw new SonarException(String.format("Module \"%s\" is already part of project \"%s\"", moduleDef.getKey(), rootInDB.getKey()));
105          }
106        }
107      }
108    
109      private void checkDeprecatedProperties(List<String> validationMessages) {
110        if (settings.getString(SONAR_PHASE) != null) {
111          validationMessages.add(String.format("Property \"%s\" is deprecated. Please remove it from your configuration.", SONAR_PHASE));
112        }
113      }
114    
115      private boolean isSubProject(ProjectDefinition moduleDef) {
116        return moduleDef.getParent() != null;
117      }
118    
119      private void validateBranch(List<String> validationMessages, @Nullable String branch) {
120        if (StringUtils.isNotEmpty(branch) && !ComponentKeys.isValidBranch(branch)) {
121          validationMessages.add(String.format("\"%s\" is not a valid branch name. "
122            + "Allowed characters are alphanumeric, '-', '_' and '.'.", branch));
123        }
124      }
125    
126    }