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 */
020 package org.sonar.core.purge;
021
022 import com.google.common.annotations.VisibleForTesting;
023 import com.google.common.collect.Lists;
024 import org.apache.commons.lang.ArrayUtils;
025 import org.apache.ibatis.session.ResultContext;
026 import org.apache.ibatis.session.ResultHandler;
027 import org.apache.ibatis.session.SqlSession;
028 import org.slf4j.Logger;
029 import org.slf4j.LoggerFactory;
030 import org.sonar.core.persistence.MyBatis;
031 import org.sonar.core.resource.ResourceDao;
032 import org.sonar.core.resource.ResourceDto;
033
034 import java.util.Collections;
035 import java.util.List;
036
037 /**
038 * @since 2.14
039 */
040 public 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 try {
054 List<ResourceDto> projects = getProjects(rootResourceId, session);
055 for (ResourceDto project : projects) {
056 LOG.info("-> Clean " + project.getLongName() + " [id=" + project.getId() + "]");
057 deleteAbortedBuilds(project, session, purgeMapper);
058 purge(project, scopesWithoutHistoricalData, session, purgeMapper);
059 }
060 for (ResourceDto project : projects) {
061 disableOrphanResources(project, session, purgeMapper);
062 }
063 } finally {
064 MyBatis.closeQuietly(session);
065 }
066 return this;
067 }
068
069 private void deleteAbortedBuilds(ResourceDto project, SqlSession session, PurgeMapper purgeMapper) {
070 if (hasAbortedBuilds(project.getId(), purgeMapper)) {
071 LOG.info("<- Delete aborted builds");
072 PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
073 .setIslast(false)
074 .setStatus(new String[]{"U"})
075 .setRootProjectId(project.getId());
076 PurgeCommands.deleteSnapshots(query, session, purgeMapper);
077 session.commit();
078 }
079 }
080
081 private boolean hasAbortedBuilds(Long projectId, PurgeMapper purgeMapper) {
082 PurgeSnapshotQuery query = PurgeSnapshotQuery.create()
083 .setIslast(false)
084 .setStatus(new String[]{"U"})
085 .setResourceId(projectId);
086 return !purgeMapper.selectSnapshotIds(query).isEmpty();
087 }
088
089 private void purge(final ResourceDto project, final String[] scopesWithoutHistoricalData, final SqlSession session, final PurgeMapper purgeMapper) {
090 List<Long> projectSnapshotIds = purgeMapper.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, session, purgeMapper);
101 session.commit();
102 }
103
104 PurgeSnapshotQuery query = PurgeSnapshotQuery.create().setRootSnapshotId(projectSnapshotId).setNotPurged(true);
105 PurgeCommands.purgeSnapshots(query, session, purgeMapper);
106 session.commit();
107
108 // must be executed at the end for reentrance
109 PurgeCommands.purgeSnapshots(PurgeSnapshotQuery.create().setId(projectSnapshotId).setNotPurged(true), session, purgeMapper);
110 session.commit();
111 }
112 }
113
114 private void disableOrphanResources(final ResourceDto project, final SqlSession session, final PurgeMapper purgeMapper) {
115 session.select("org.sonar.core.purge.PurgeMapper.selectResourceIdsToDisable", project.getId(), new ResultHandler() {
116 public void handleResult(ResultContext resultContext) {
117 Long resourceId = (Long) resultContext.getResultObject();
118 if (resourceId != null) {
119 disableResource(resourceId, purgeMapper);
120 }
121 }
122 });
123 session.commit();
124 }
125
126 public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId) {
127 SqlSession session = mybatis.openBatchSession();
128 try {
129 PurgeMapper mapper = session.getMapper(PurgeMapper.class);
130 List<PurgeableSnapshotDto> result = Lists.newArrayList();
131 result.addAll(mapper.selectPurgeableSnapshotsWithEvents(resourceId));
132 result.addAll(mapper.selectPurgeableSnapshotsWithoutEvents(resourceId));
133 Collections.sort(result);// sort by date
134 return result;
135 } finally {
136 MyBatis.closeQuietly(session);
137 }
138 }
139
140 public PurgeDao deleteResourceTree(long rootProjectId) {
141 final SqlSession session = mybatis.openBatchSession();
142 final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
143 final PurgeVendorMapper vendorMapper = session.getMapper(PurgeVendorMapper.class);
144 try {
145 deleteProject(rootProjectId, session, mapper, vendorMapper);
146 return this;
147 } finally {
148 MyBatis.closeQuietly(session);
149 }
150 }
151
152 private void deleteProject(long rootProjectId, SqlSession session, PurgeMapper mapper, PurgeVendorMapper vendorMapper) {
153 List<Long> childrenIds = mapper.selectProjectIdsByRootId(rootProjectId);
154 for (Long childId : childrenIds) {
155 deleteProject(childId, session, mapper, vendorMapper);
156 }
157
158 List<Long> resourceIds = mapper.selectResourceIdsByRootId(rootProjectId);
159 PurgeCommands.deleteResources(resourceIds, session, mapper, vendorMapper);
160 session.commit();
161 }
162
163 @VisibleForTesting
164 void disableResource(long resourceId, PurgeMapper mapper) {
165 mapper.deleteResourceIndex(resourceId);
166 mapper.setSnapshotIsLastToFalse(resourceId);
167 mapper.disableResource(resourceId);
168 mapper.closeResourceReviews(resourceId);
169 }
170
171 public PurgeDao deleteSnapshots(PurgeSnapshotQuery query) {
172 final SqlSession session = mybatis.openBatchSession();
173 try {
174 final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
175 PurgeCommands.deleteSnapshots(query, session, mapper);
176 session.commit();
177 return this;
178
179 } finally {
180 MyBatis.closeQuietly(session);
181 }
182 }
183
184 /**
185 * Load the whole tree of projects, including the project given in parameter.
186 */
187 private List<ResourceDto> getProjects(long rootProjectId, SqlSession session) {
188 List<ResourceDto> projects = Lists.newArrayList();
189 projects.add(resourceDao.getResource(rootProjectId, session));
190 projects.addAll(resourceDao.getDescendantProjects(rootProjectId, session));
191 return projects;
192 }
193
194 }