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}