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 org.sonar.api.resources.Language;
023
024 import com.google.common.collect.Maps;
025 import org.apache.commons.lang.ObjectUtils;
026 import org.apache.commons.lang.StringUtils;
027 import org.sonar.api.batch.fs.InputFile;
028 import org.sonar.api.database.DatabaseSession;
029 import org.sonar.api.database.model.ResourceModel;
030 import org.sonar.api.database.model.Snapshot;
031 import org.sonar.api.resources.File;
032 import org.sonar.api.resources.Library;
033 import org.sonar.api.resources.Project;
034 import org.sonar.api.resources.Qualifiers;
035 import org.sonar.api.resources.Resource;
036 import org.sonar.api.resources.ResourceUtils;
037 import org.sonar.api.resources.Scopes;
038 import org.sonar.api.security.ResourcePermissions;
039 import org.sonar.api.utils.SonarException;
040
041 import javax.annotation.CheckForNull;
042 import javax.annotation.Nullable;
043 import javax.persistence.NonUniqueResultException;
044 import javax.persistence.Query;
045
046 import java.util.Date;
047 import java.util.Iterator;
048 import java.util.List;
049 import java.util.Map;
050
051 public final class DefaultResourcePersister implements ResourcePersister {
052
053 private static final String RESOURCE_ID = "resourceId";
054 private static final String LAST = "last";
055 private static final String VERSION = "version";
056 private static final String SCOPE = "scope";
057 private static final String QUALIFIER = "qualifier";
058
059 private final DatabaseSession session;
060 private final Map<Resource, Snapshot> snapshotsByResource = Maps.newHashMap();
061 private final ResourcePermissions permissions;
062 private final SnapshotCache snapshotCache;
063 private final ResourceCache resourceCache;
064
065 public DefaultResourcePersister(DatabaseSession session, ResourcePermissions permissions, SnapshotCache snapshotCache, ResourceCache resourceCache) {
066 this.session = session;
067 this.permissions = permissions;
068 this.snapshotCache = snapshotCache;
069 this.resourceCache = resourceCache;
070 }
071
072 public Snapshot saveProject(Project project, @Nullable Project parent) {
073 Snapshot snapshot = snapshotsByResource.get(project);
074 if (snapshot == null) {
075 snapshot = persistProject(project, parent);
076 addToCache(project, snapshot);
077 }
078 return snapshot;
079 }
080
081 private void addToCache(Resource resource, Snapshot snapshot) {
082 if (snapshot != null) {
083 snapshotsByResource.put(resource, snapshot);
084 resourceCache.add(resource);
085 if (!(resource instanceof Library)) {
086 // Maven libraries can have the same effective key than a project so we can't cache by effectiveKey
087 snapshotCache.put(resource.getEffectiveKey(), snapshot);
088 }
089 }
090 }
091
092 private Snapshot persistProject(Project project, @Nullable Project parent) {
093 // temporary hack
094 project.setEffectiveKey(project.getKey());
095
096 ResourceModel model = findOrCreateModel(project);
097 // Used by ResourceKeyMigration in order to know that a project has already being migrated
098 model.setDeprecatedKey(project.getKey());
099 // language is null for project since multi-language support
100 model.setLanguageKey(null);
101
102 // For views
103 if (project instanceof ResourceCopy) {
104 model.setCopyResourceId(((ResourceCopy) project).getCopyResourceId());
105 }
106
107 Snapshot parentSnapshot = null;
108 if (parent != null) {
109 // assume that the parent project has already been saved
110 parentSnapshot = snapshotsByResource.get(project.getParent());
111 model.setRootId((Integer) ObjectUtils.defaultIfNull(parentSnapshot.getRootProjectId(), parentSnapshot.getResourceId()));
112 } else {
113 model.setRootId(null);
114 }
115 model = session.save(model);
116 project.setId(model.getId());
117
118 Snapshot snapshot = new Snapshot(model, parentSnapshot);
119 snapshot.setVersion(project.getAnalysisVersion());
120 snapshot.setCreatedAt(project.getAnalysisDate());
121 snapshot.setBuildDate(new Date());
122 snapshot = session.save(snapshot);
123 session.commit();
124
125 if (!permissions.hasRoles(project)) {
126 permissions.grantDefaultRoles(project);
127 }
128
129 return snapshot;
130 }
131
132 @CheckForNull
133 public Snapshot getSnapshot(@Nullable Resource reference) {
134 return snapshotsByResource.get(reference);
135 }
136
137 public Snapshot getSnapshotOrFail(Resource resource) {
138 Snapshot snapshot = getSnapshot(resource);
139 if (snapshot == null) {
140 throw new ResourceNotPersistedException(resource);
141 }
142 return snapshot;
143 }
144
145 @Override
146 public Snapshot getSnapshotOrFail(InputFile inputFile) {
147 return getSnapshotOrFail(fromInputFile(inputFile));
148 }
149
150 private Resource fromInputFile(InputFile inputFile) {
151 return File.create(inputFile.relativePath());
152 }
153
154 /**
155 * just for unit tests
156 */
157 Map<Resource, Snapshot> getSnapshotsByResource() {
158 return snapshotsByResource;
159 }
160
161 public Snapshot saveResource(Project project, Resource resource) {
162 return saveResource(project, resource, null);
163 }
164
165 public Snapshot saveResource(Project project, Resource resource, @Nullable Resource parent) {
166 Snapshot snapshot = snapshotsByResource.get(resource);
167 if (snapshot == null) {
168 snapshot = persist(project, resource, parent);
169 addToCache(resource, snapshot);
170 }
171 return snapshot;
172 }
173
174 private Snapshot persist(Project project, Resource resource, @Nullable Resource parent) {
175 Snapshot snapshot;
176 if (resource instanceof Project) {
177 // should not occur, please use the method saveProject()
178 snapshot = persistProject((Project) resource, project);
179
180 } else if (resource instanceof Library) {
181 snapshot = persistLibrary(project, (Library) resource);
182
183 } else {
184 snapshot = persistFileOrDirectory(project, resource, parent);
185 }
186
187 return snapshot;
188 }
189
190 private Snapshot persistLibrary(Project project, Library library) {
191 ResourceModel model = findOrCreateModel(library);
192 model = session.save(model);
193 // TODO to be removed
194 library.setId(model.getId());
195 library.setEffectiveKey(library.getKey());
196
197 Snapshot snapshot = findLibrarySnapshot(model.getId(), library.getVersion());
198 if (snapshot == null) {
199 snapshot = new Snapshot(model, null);
200 snapshot.setCreatedAt(project.getAnalysisDate());
201 snapshot.setBuildDate(new Date());
202 snapshot.setVersion(library.getVersion());
203 snapshot.setStatus(Snapshot.STATUS_PROCESSED);
204
205 // see http://jira.codehaus.org/browse/SONAR-1850
206 // The qualifier must be LIB, even if the resource is TRK, because this snapshot has no measures.
207 snapshot.setQualifier(Qualifiers.LIBRARY);
208 snapshot = session.save(snapshot);
209 }
210 session.commit();
211 return snapshot;
212 }
213
214 private Snapshot findLibrarySnapshot(Integer resourceId, String version) {
215 Query query = session.createQuery("from " + Snapshot.class.getSimpleName() +
216 " s WHERE s.resourceId=:resourceId AND s.version=:version AND s.scope=:scope AND s.qualifier<>:qualifier AND s.last=:last");
217 query.setParameter(RESOURCE_ID, resourceId);
218 query.setParameter(VERSION, version);
219 query.setParameter(SCOPE, Scopes.PROJECT);
220 query.setParameter(QUALIFIER, Qualifiers.LIBRARY);
221 query.setParameter(LAST, Boolean.TRUE);
222 List<Snapshot> snapshots = query.getResultList();
223 if (snapshots.isEmpty()) {
224 snapshots = session.getResults(Snapshot.class, RESOURCE_ID, resourceId, VERSION, version, SCOPE, Scopes.PROJECT, QUALIFIER, Qualifiers.LIBRARY);
225 }
226 return snapshots.isEmpty() ? null : snapshots.get(0);
227 }
228
229 /**
230 * Everything except project and library
231 */
232 private Snapshot persistFileOrDirectory(Project project, Resource resource, @Nullable Resource parentReference) {
233 Snapshot moduleSnapshot = snapshotsByResource.get(project);
234 Integer moduleId = moduleSnapshot.getResourceId();
235 ResourceModel model = findOrCreateModel(resource);
236 model.setRootId(moduleId);
237 model = session.save(model);
238 resource.setId(model.getId());
239
240 Snapshot parentSnapshot = (Snapshot) ObjectUtils.defaultIfNull(getSnapshot(parentReference), moduleSnapshot);
241 Snapshot snapshot = new Snapshot(model, parentSnapshot);
242 snapshot.setBuildDate(new Date());
243 snapshot = session.save(snapshot);
244 session.commit();
245 return snapshot;
246 }
247
248 @CheckForNull
249 public Snapshot getLastSnapshot(Snapshot snapshot, boolean onlyOlder) {
250 String hql = "SELECT s FROM " + Snapshot.class.getSimpleName() + " s WHERE s.last=:last AND s.resourceId=:resourceId";
251 if (onlyOlder) {
252 hql += " AND s.createdAt<:date";
253 }
254 Query query = session.createQuery(hql);
255 query.setParameter(LAST, true);
256 query.setParameter(RESOURCE_ID, snapshot.getResourceId());
257 if (onlyOlder) {
258 query.setParameter("date", snapshot.getCreatedAt());
259 }
260 return session.getSingleResult(query, null);
261 }
262
263 public void clear() {
264 // we keep cache of projects
265 for (Iterator<Map.Entry<Resource, Snapshot>> it = snapshotsByResource.entrySet().iterator(); it.hasNext();) {
266 Map.Entry<Resource, Snapshot> entry = it.next();
267 if (!ResourceUtils.isSet(entry.getKey())) {
268 it.remove();
269 }
270 }
271 }
272
273 private ResourceModel findOrCreateModel(Resource resource) {
274 ResourceModel model;
275 try {
276 model = session.getSingleResult(ResourceModel.class, "key", resource.getEffectiveKey());
277 if (model == null) {
278 if (StringUtils.isBlank(resource.getEffectiveKey())) {
279 throw new SonarException("Unable to persist resource " + resource.toString() + ". Resource effective key is blank. This may be caused by an outdated plugin.");
280 }
281 model = createModel(resource);
282
283 } else {
284 mergeModel(model, resource);
285 }
286 return model;
287
288 } catch (NonUniqueResultException e) {
289 throw new SonarException("The resource '" + resource.getEffectiveKey() + "' is duplicated in database.", e);
290 }
291 }
292
293 static ResourceModel createModel(Resource resource) {
294 ResourceModel model = new ResourceModel();
295 model.setEnabled(Boolean.TRUE);
296 model.setDescription(resource.getDescription());
297 model.setKey(resource.getEffectiveKey());
298 model.setPath(resource.getPath());
299 Language language = resource.getLanguage();
300 if (language != null) {
301 model.setLanguageKey(language.getKey());
302 }
303 if (StringUtils.isNotBlank(resource.getName())) {
304 model.setName(resource.getName());
305 } else {
306 model.setName(resource.getKey());
307 }
308 model.setLongName(resource.getLongName());
309 model.setScope(resource.getScope());
310 model.setQualifier(resource.getQualifier());
311 return model;
312 }
313
314 static void mergeModel(ResourceModel model, Resource resource) {
315 model.setEnabled(true);
316 model.setKey(resource.getEffectiveKey());
317 if (StringUtils.isNotBlank(resource.getName())) {
318 model.setName(resource.getName());
319 }
320 if (StringUtils.isNotBlank(resource.getLongName())) {
321 model.setLongName(resource.getLongName());
322 }
323 if (StringUtils.isNotBlank(resource.getDescription())) {
324 model.setDescription(resource.getDescription());
325 }
326 if (StringUtils.isNotBlank(resource.getPath())) {
327 model.setPath(resource.getPath());
328 }
329 if (!ResourceUtils.isLibrary(resource)) {
330 model.setScope(resource.getScope());
331 model.setQualifier(resource.getQualifier());
332 }
333 Language language = resource.getLanguage();
334 if (language != null) {
335 model.setLanguageKey(language.getKey());
336 }
337 }
338 }