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 LOG.info("-> Disable resources in " + project.getLongName() + " [id=" + project.getId() + "]");
116 session.select("org.sonar.core.purge.PurgeMapper.selectResourceIdsToDisable", project.getId(), new ResultHandler() {
117 public void handleResult(ResultContext resultContext) {
118 Long resourceId = (Long) resultContext.getResultObject();
119 if (resourceId != null) {
120 disableResource(resourceId, purgeMapper);
121 }
122 }
123 });
124 session.commit();
125 }
126
127 public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId) {
128 SqlSession session = mybatis.openBatchSession();
129 try {
130 PurgeMapper mapper = session.getMapper(PurgeMapper.class);
131 List<PurgeableSnapshotDto> result = Lists.newArrayList();
132 result.addAll(mapper.selectPurgeableSnapshotsWithEvents(resourceId));
133 result.addAll(mapper.selectPurgeableSnapshotsWithoutEvents(resourceId));
134 Collections.sort(result);// sort by date
135 return result;
136 } finally {
137 MyBatis.closeQuietly(session);
138 }
139 }
140
141 public PurgeDao deleteProject(long rootProjectId) {
142 final SqlSession session = mybatis.openBatchSession();
143 final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
144 final PurgeVendorMapper vendorMapper = session.getMapper(PurgeVendorMapper.class);
145 try {
146 deleteProject(rootProjectId, session, mapper, vendorMapper);
147 return this;
148 } finally {
149 MyBatis.closeQuietly(session);
150 }
151 }
152
153 private void deleteProject(long rootProjectId, SqlSession session, PurgeMapper mapper, PurgeVendorMapper vendorMapper) {
154 List<Long> childrenIds = mapper.selectProjectIdsByRootId(rootProjectId);
155 for (Long childId : childrenIds) {
156 deleteProject(childId, session, mapper, vendorMapper);
157 }
158
159 List<Long> resourceIds = mapper.selectResourceIdsByRootId(rootProjectId);
160 PurgeCommands.deleteResources(resourceIds, session, mapper, vendorMapper);
161 session.commit();
162 }
163
164 @VisibleForTesting
165 void disableResource(long resourceId, PurgeMapper mapper) {
166 mapper.deleteResourceIndex(resourceId);
167 mapper.setSnapshotIsLastToFalse(resourceId);
168 mapper.disableResource(resourceId);
169 mapper.closeResourceReviews(resourceId);
170 }
171
172 public PurgeDao deleteSnapshots(PurgeSnapshotQuery query) {
173 final SqlSession session = mybatis.openBatchSession();
174 try {
175 final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
176 PurgeCommands.deleteSnapshots(query, session, mapper);
177 session.commit();
178 return this;
179
180 } finally {
181 MyBatis.closeQuietly(session);
182 }
183 }
184
185 /**
186 * Load the whole tree of projects, including the project given in parameter.
187 */
188 private List<ResourceDto> getProjects(long rootProjectId, SqlSession session) {
189 List<ResourceDto> projects = Lists.newArrayList();
190 projects.add(resourceDao.getResource(rootProjectId, session));
191 projects.addAll(resourceDao.getDescendantProjects(rootProjectId, session));
192 return projects;
193 }
194
195 }