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    }