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    }