001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.core.purge;
021
022import com.google.common.annotations.VisibleForTesting;
023import com.google.common.collect.Lists;
024import org.apache.commons.lang.ArrayUtils;
025import org.apache.ibatis.session.ResultContext;
026import org.apache.ibatis.session.ResultHandler;
027import org.apache.ibatis.session.SqlSession;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030import org.sonar.core.persistence.MyBatis;
031import org.sonar.core.resource.ResourceDao;
032import org.sonar.core.resource.ResourceDto;
033
034import java.util.Collections;
035import java.util.List;
036
037/**
038 * @since 2.14
039 */
040public class PurgeDao {
041  private final MyBatis mybatis;
042  private final ResourceDao resourceDao;
043  private static final Logger LOG = LoggerFactory.getLogger(PurgeDao.class);
044
045  public PurgeDao(MyBatis mybatis, ResourceDao resourceDao) {
046    this.mybatis = mybatis;
047    this.resourceDao = resourceDao;
048  }
049
050  public PurgeDao purge(long rootResourceId, String[] scopesWithoutHistoricalData) {
051    SqlSession session = mybatis.openBatchSession();
052    PurgeMapper purgeMapper = session.getMapper(PurgeMapper.class);
053    PurgeCommands commands = new PurgeCommands(session, purgeMapper, session.getMapper(PurgeVendorMapper.class));
054    try {
055      List<ResourceDto> projects = getProjects(rootResourceId, session);
056      for (ResourceDto project : projects) {
057        LOG.info("-> Clean " + project.getLongName() + " [id=" + project.getId() + "]");
058        deleteAbortedBuilds(project, commands);
059        purge(project, scopesWithoutHistoricalData, commands);
060      }
061      for (ResourceDto project : projects) {
062        disableOrphanResources(project, session, purgeMapper);
063      }
064    } finally {
065      MyBatis.closeQuietly(session);
066    }
067    return this;
068  }
069
070  private void deleteAbortedBuilds(ResourceDto project, PurgeCommands commands) {
071    if (hasAbortedBuilds(project.getId(), commands)) {
072      LOG.info("<- Delete aborted builds");
073      PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
074        .setIslast(false)
075        .setStatus(new String[]{"U"})
076        .setRootProjectId(project.getId());
077      commands.deleteSnapshots(query);
078    }
079  }
080
081  private boolean hasAbortedBuilds(Long projectId, PurgeCommands commands) {
082    PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
083      .setIslast(false)
084      .setStatus(new String[]{"U"})
085      .setResourceId(projectId);
086    return !commands.selectSnapshotIds(query).isEmpty();
087  }
088
089  private void purge(ResourceDto project, String[] scopesWithoutHistoricalData, PurgeCommands purgeCommands) {
090    List<Long> projectSnapshotIds = purgeCommands.selectSnapshotIds(
091      PurgeSnapshotQuery.create().setResourceId(project.getId()).setIslast(false).setNotPurged(true)
092    );
093    for (final Long projectSnapshotId : projectSnapshotIds) {
094      LOG.info("<- Clean snapshot " + projectSnapshotId);
095      if (!ArrayUtils.isEmpty(scopesWithoutHistoricalData)) {
096        PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
097          .setIslast(false)
098          .setScopes(scopesWithoutHistoricalData)
099          .setRootSnapshotId(projectSnapshotId);
100        purgeCommands.deleteSnapshots(query);
101      }
102
103      PurgeSnapshotQuery query = PurgeSnapshotQuery.create().setRootSnapshotId(projectSnapshotId).setNotPurged(true);
104      purgeCommands.purgeSnapshots(query);
105
106      // must be executed at the end for reentrance
107      purgeCommands.purgeSnapshots(PurgeSnapshotQuery.create().setId(projectSnapshotId).setNotPurged(true));
108    }
109  }
110
111  private void disableOrphanResources(final ResourceDto project, final SqlSession session, final PurgeMapper purgeMapper) {
112    session.select("org.sonar.core.purge.PurgeMapper.selectResourceIdsToDisable", project.getId(), new ResultHandler() {
113      public void handleResult(ResultContext resultContext) {
114        Long resourceId = (Long) resultContext.getResultObject();
115        if (resourceId != null) {
116          disableResource(resourceId, purgeMapper);
117        }
118      }
119    });
120    session.commit();
121  }
122
123  public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId) {
124    SqlSession session = mybatis.openBatchSession();
125    try {
126      PurgeMapper mapper = session.getMapper(PurgeMapper.class);
127      List<PurgeableSnapshotDto> result = Lists.newArrayList();
128      result.addAll(mapper.selectPurgeableSnapshotsWithEvents(resourceId));
129      result.addAll(mapper.selectPurgeableSnapshotsWithoutEvents(resourceId));
130      Collections.sort(result);// sort by date
131      return result;
132    } finally {
133      MyBatis.closeQuietly(session);
134    }
135  }
136
137  public PurgeDao deleteResourceTree(long rootProjectId) {
138    final SqlSession session = mybatis.openBatchSession();
139    final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
140    try {
141      deleteProject(rootProjectId, mapper, new PurgeCommands(session));
142      return this;
143    } finally {
144      MyBatis.closeQuietly(session);
145    }
146  }
147
148  private void deleteProject(long rootProjectId, PurgeMapper mapper, PurgeCommands commands) {
149    List<Long> childrenIds = mapper.selectProjectIdsByRootId(rootProjectId);
150    for (Long childId : childrenIds) {
151      deleteProject(childId, mapper, commands);
152    }
153
154    List<Long> resourceIds = mapper.selectResourceIdsByRootId(rootProjectId);
155    commands.deleteResources(resourceIds);
156  }
157
158  @VisibleForTesting
159  void disableResource(long resourceId, PurgeMapper mapper) {
160    mapper.deleteResourceIndex(resourceId);
161    mapper.setSnapshotIsLastToFalse(resourceId);
162    mapper.disableResource(resourceId);
163    mapper.closeResourceReviews(resourceId);
164  }
165
166  public PurgeDao deleteSnapshots(PurgeSnapshotQuery query) {
167    final SqlSession session = mybatis.openBatchSession();
168    try {
169      new PurgeCommands(session).deleteSnapshots(query);
170      return this;
171
172    } finally {
173      MyBatis.closeQuietly(session);
174    }
175  }
176
177  /**
178   * Load the whole tree of projects, including the project given in parameter.
179   */
180  private List<ResourceDto> getProjects(long rootProjectId, SqlSession session) {
181    List<ResourceDto> projects = Lists.newArrayList();
182    projects.add(resourceDao.getResource(rootProjectId, session));
183    projects.addAll(resourceDao.getDescendantProjects(rootProjectId, session));
184    return projects;
185  }
186
187}