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 }