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.index;
021
022 import com.google.common.annotations.VisibleForTesting;
023 import org.apache.commons.lang.StringUtils;
024 import org.slf4j.Logger;
025 import org.slf4j.LoggerFactory;
026 import org.sonar.api.BatchComponent;
027 import org.sonar.api.batch.fs.FileSystem;
028 import org.sonar.api.batch.fs.InputFile;
029 import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
030 import org.sonar.api.database.DatabaseSession;
031 import org.sonar.api.database.model.ResourceModel;
032 import org.sonar.api.resources.Directory;
033 import org.sonar.api.resources.File;
034 import org.sonar.api.resources.Project;
035 import org.sonar.api.resources.Qualifiers;
036 import org.sonar.api.resources.Resource;
037 import org.sonar.api.resources.Scopes;
038 import org.sonar.api.utils.PathUtils;
039 import org.sonar.batch.util.DeprecatedKeyUtils;
040
041 import java.util.HashMap;
042 import java.util.List;
043 import java.util.Map;
044
045 public class ResourceKeyMigration implements BatchComponent {
046
047 private static final String UNABLE_TO_UPDATE_COMPONENT_NO_MATCH_WAS_FOUND = "Unable to update component {}. No match was found.";
048 private static final String COMPONENT_CHANGED_TO = "Component {} changed to {}";
049 private final Logger logger;
050 private final DatabaseSession session;
051
052 private boolean migrationNeeded = false;
053
054 public ResourceKeyMigration(DatabaseSession session) {
055 this(session, LoggerFactory.getLogger(ResourceKeyMigration.class));
056 }
057
058 @VisibleForTesting
059 ResourceKeyMigration(DatabaseSession session, Logger logger) {
060 this.session = session;
061 this.logger = logger;
062 }
063
064 public void checkIfMigrationNeeded(Project rootProject) {
065 ResourceModel model = session.getSingleResult(ResourceModel.class, "key", rootProject.getEffectiveKey());
066 if (model != null && StringUtils.isBlank(model.getDeprecatedKey())) {
067 this.migrationNeeded = true;
068 }
069 }
070
071 public void migrateIfNeeded(Project module, FileSystem fs) {
072 if (migrationNeeded) {
073 migrateIfNeeded(module, fs.inputFiles(fs.predicates().all()));
074 }
075 }
076
077 void migrateIfNeeded(Project module, Iterable<InputFile> inputFiles) {
078 logger.info("Update component keys");
079 Map<String, InputFile> deprecatedFileKeyMapper = new HashMap<String, InputFile>();
080 Map<String, InputFile> deprecatedTestKeyMapper = new HashMap<String, InputFile>();
081 Map<String, String> deprecatedDirectoryKeyMapper = new HashMap<String, String>();
082 for (InputFile inputFile : inputFiles) {
083 String deprecatedKey = ((DeprecatedDefaultInputFile) inputFile).deprecatedKey();
084 if (deprecatedKey != null) {
085 if (InputFile.Type.TEST == inputFile.type() && !deprecatedTestKeyMapper.containsKey(deprecatedKey)) {
086 deprecatedTestKeyMapper.put(deprecatedKey, inputFile);
087 } else if (InputFile.Type.MAIN == inputFile.type() && !deprecatedFileKeyMapper.containsKey(deprecatedKey)) {
088 deprecatedFileKeyMapper.put(deprecatedKey, inputFile);
089 }
090 }
091 }
092
093 ResourceModel moduleModel = session.getSingleResult(ResourceModel.class, "key", module.getEffectiveKey());
094 int moduleId = moduleModel.getId();
095 migrateFiles(module, deprecatedFileKeyMapper, deprecatedTestKeyMapper, deprecatedDirectoryKeyMapper, moduleId);
096 migrateDirectories(deprecatedDirectoryKeyMapper, moduleId);
097 session.commit();
098 }
099
100 private void migrateFiles(Project module, Map<String, InputFile> deprecatedFileKeyMapper, Map<String, InputFile> deprecatedTestKeyMapper,
101 Map<String, String> deprecatedDirectoryKeyMapper,
102 int moduleId) {
103 // Find all FIL or CLA resources for this module
104 StringBuilder hql = newResourceQuery()
105 .append(" and scope = '").append(Scopes.FILE).append("' order by qualifier, key");
106 Map<String, ResourceModel> disabledResourceByKey = loadDisabledResources(moduleId, hql);
107 List<ResourceModel> resources = loadEnabledResources(moduleId, hql);
108 for (ResourceModel resourceModel : resources) {
109 String oldEffectiveKey = resourceModel.getKey();
110 boolean isTest = Qualifiers.UNIT_TEST_FILE.equals(resourceModel.getQualifier());
111 InputFile matchedFile = findInputFile(deprecatedFileKeyMapper, deprecatedTestKeyMapper, oldEffectiveKey, isTest);
112 if (matchedFile != null) {
113 String newEffectiveKey = ((DeprecatedDefaultInputFile) matchedFile).key();
114 // Now compute migration of the parent dir
115 String oldKey = StringUtils.substringAfterLast(oldEffectiveKey, ":");
116 Resource sonarFile;
117 String parentOldKey;
118 if ("java".equals(resourceModel.getLanguageKey())) {
119 parentOldKey = String.format("%s:%s", module.getEffectiveKey(), DeprecatedKeyUtils.getJavaFileParentDeprecatedKey(oldKey));
120 } else {
121 sonarFile = new File(oldKey);
122 parentOldKey = String.format("%s:%s", module.getEffectiveKey(), sonarFile.getParent().getDeprecatedKey());
123 }
124 String parentNewKey = String.format("%s:%s", module.getEffectiveKey(), getParentKey(matchedFile));
125 if (!deprecatedDirectoryKeyMapper.containsKey(parentOldKey)) {
126 deprecatedDirectoryKeyMapper.put(parentOldKey, parentNewKey);
127 } else if (!parentNewKey.equals(deprecatedDirectoryKeyMapper.get(parentOldKey))) {
128 logger.warn("Directory with key " + parentOldKey + " matches both " + deprecatedDirectoryKeyMapper.get(parentOldKey) + " and "
129 + parentNewKey + ". First match is arbitrary chosen.");
130 }
131 updateKey(resourceModel, newEffectiveKey, disabledResourceByKey);
132 resourceModel.setDeprecatedKey(oldEffectiveKey);
133 logger.info(COMPONENT_CHANGED_TO, oldEffectiveKey, newEffectiveKey);
134 } else {
135 logger.warn(UNABLE_TO_UPDATE_COMPONENT_NO_MATCH_WAS_FOUND, oldEffectiveKey);
136 }
137 }
138 }
139
140 private void updateKey(ResourceModel resourceModel, String newEffectiveKey, Map<String, ResourceModel> disabledResourceByKey) {
141 // Look for disabled resource with conflicting key
142 if (disabledResourceByKey.containsKey(newEffectiveKey)) {
143 ResourceModel duplicateDisabledResource = disabledResourceByKey.get(newEffectiveKey);
144 String disabledKey = newEffectiveKey + "_renamed_by_resource_key_migration";
145 duplicateDisabledResource.setKey(disabledKey);
146 logger.info(COMPONENT_CHANGED_TO, newEffectiveKey, disabledKey);
147 }
148 resourceModel.setKey(newEffectiveKey);
149 }
150
151 private StringBuilder newResourceQuery() {
152 return new StringBuilder().append("from ")
153 .append(ResourceModel.class.getSimpleName())
154 .append(" where enabled = :enabled")
155 .append(" and rootId = :rootId ");
156 }
157
158 private InputFile findInputFile(Map<String, InputFile> deprecatedFileKeyMapper, Map<String, InputFile> deprecatedTestKeyMapper, String oldEffectiveKey, boolean isTest) {
159 if (isTest) {
160 return deprecatedTestKeyMapper.get(oldEffectiveKey);
161 } else {
162 return deprecatedFileKeyMapper.get(oldEffectiveKey);
163 }
164 }
165
166 private void migrateDirectories(Map<String, String> deprecatedDirectoryKeyMapper, int moduleId) {
167 // Find all DIR resources for this module
168 StringBuilder hql = newResourceQuery()
169 .append(" and qualifier = '").append(Qualifiers.DIRECTORY).append("'");
170 Map<String, ResourceModel> disabledResourceByKey = loadDisabledResources(moduleId, hql);
171 List<ResourceModel> resources = loadEnabledResources(moduleId, hql);
172 for (ResourceModel resourceModel : resources) {
173 String oldEffectiveKey = resourceModel.getKey();
174 if (deprecatedDirectoryKeyMapper.containsKey(oldEffectiveKey)) {
175 String newEffectiveKey = deprecatedDirectoryKeyMapper.get(oldEffectiveKey);
176 updateKey(resourceModel, newEffectiveKey, disabledResourceByKey);
177 resourceModel.setDeprecatedKey(oldEffectiveKey);
178 logger.info(COMPONENT_CHANGED_TO, oldEffectiveKey, newEffectiveKey);
179 } else {
180 logger.warn(UNABLE_TO_UPDATE_COMPONENT_NO_MATCH_WAS_FOUND, oldEffectiveKey);
181 }
182 }
183 }
184
185 private List<ResourceModel> loadEnabledResources(int moduleId, StringBuilder hql) {
186 return session.createQuery(hql.toString())
187 .setParameter("rootId", moduleId)
188 .setParameter("enabled", true)
189 .getResultList();
190 }
191
192 private Map<String, ResourceModel> loadDisabledResources(int moduleId, StringBuilder hql) {
193 List<ResourceModel> disabledResources = session.createQuery(hql.toString())
194 .setParameter("rootId", moduleId)
195 .setParameter("enabled", false)
196 .getResultList();
197 Map<String, ResourceModel> disabledResourceByKey = new HashMap<String, ResourceModel>();
198 for (ResourceModel disabledResourceModel : disabledResources) {
199 disabledResourceByKey.put(disabledResourceModel.getKey(), disabledResourceModel);
200 }
201 return disabledResourceByKey;
202 }
203
204 private String getParentKey(InputFile matchedFile) {
205 String filePath = PathUtils.sanitize(matchedFile.relativePath());
206 String parentFolderPath;
207 if (filePath.contains(Directory.SEPARATOR)) {
208 parentFolderPath = StringUtils.substringBeforeLast(filePath, Directory.SEPARATOR);
209 } else {
210 parentFolderPath = Directory.SEPARATOR;
211 }
212 return parentFolderPath;
213 }
214 }