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 }