Java tutorial
/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.vacuum; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.enonic.cms.framework.blob.gc.GarbageCollector; import com.enonic.cms.core.security.SecurityService; import com.enonic.cms.core.security.user.User; import com.enonic.cms.core.security.userstore.MemberOfResolver; import com.enonic.cms.store.support.ConnectionFactory; @Component public class VacuumServiceImpl implements VacuumService { private static final int BATCH_SIZE = 10; private static final String VACUUM_READ_LOGS_SQL = "DELETE FROM tLogEntry WHERE len_lTypeKey = 7"; @Autowired protected GarbageCollector garbageCollector; @Autowired protected ConnectionFactory connectionFactory; @Autowired protected SecurityService securityService; @Autowired protected MemberOfResolver memberOfResolver; private ProgressInfo progressInfo = new ProgressInfo(); private final Map<String, List<Integer>> queryCache = new HashMap<String, List<Integer>>(); /** * Clean read logs. */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void cleanReadLogs() { if (progressInfo.isInProgress() || !isAdmin()) { return; } try { startProgress("Cleaning read logs..."); final Connection conn = connectionFactory.getConnection(true); setProgress("Vacuum read logs...", 5); vacuumReadLogs(conn); } catch (final Exception e) { setProgress("Failed to clean read logs: " + e.getMessage(), 100); progressInfo.setInProgress(false); throw new RuntimeException("Failed to clean read logs", e); } finally { finishProgress(); } } /** * Clean unused content. */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, timeout = 3600) public void cleanUnusedContent() { if (progressInfo.isInProgress() || !isAdmin()) { return; } try { startProgress("Cleaning unused content..."); final Connection conn = connectionFactory.getConnection(true); setProgress("Vacuum binaries...", 5); vacuumBinaries(conn); setProgress("Vacuum contents...", 20); vacuumContents(conn); setProgress("Vacuum categories...", 40); vacuumCategories(conn); setProgress("Vacuum archives...", 60); vacuumArchives(conn); setProgress("Vacuum blob store...", 80); vacuumBlobStore(); } catch (final Exception e) { setProgress("Failed to clean unused content: " + e.getMessage(), 100); progressInfo.setInProgress(false); throw new RuntimeException("Failed to clean unused content", e); } finally { finishProgress(); queryCache.clear(); } } /** * returns progress info about either Clean unused content or Clean read logs. */ public ProgressInfo getProgressInfo() { if (!isAdmin()) { return ProgressInfo.NONE; } return progressInfo; } private void startProgress(final String logLine) { setProgress(logLine, 0); progressInfo.setInProgress(true); } private void setProgress(final String logLine, final int percent) { progressInfo.setLogLine(logLine); progressInfo.setPercent(percent); } private void finishProgress() { if (progressInfo.isInProgress()) { setProgress("Finished. Last job was executed at " + new Date().toString(), 100); progressInfo.setInProgress(false); } } private boolean isAdmin() { final User user = securityService.getLoggedInAdminConsoleUser(); return memberOfResolver.hasEnterpriseAdminPowers(user.getKey()); } /** * Vacuum binaries. */ private void vacuumBinaries(final Connection conn) throws Exception { executeStatements(conn, VacuumContentSQL.VACUUM_BINARIES_STATEMENTS); } /** * Vacuum contents. */ private void vacuumContents(final Connection conn) throws Exception { executeStatements(conn, VacuumContentSQL.VACUUM_CONTENT_STATEMENTS); } /** * Vacuum categories. */ private void vacuumCategories(final Connection conn) throws Exception { executeStatements(conn, VacuumContentSQL.VACUUM_CATEGORIES_STATEMENTS); } /** * Vacuum arvhives. */ private void vacuumArchives(final Connection conn) throws Exception { executeStatements(conn, VacuumContentSQL.VACUUM_ARCHIVES_STATEMENTS); } /** * Vacuum read logs. */ private void vacuumReadLogs(final Connection conn) throws Exception { executeStatements(conn, new String[] { VACUUM_READ_LOGS_SQL }); } private void vacuumBlobStore() { this.garbageCollector.process(); } /** * Execute a list of statements. */ private void executeStatements(final Connection conn, final String[] sqlList) throws Exception { for (final String sql : sqlList) { executeStatementBatch(conn, sql); } } /** * Execute statement. */ private void executeStatement(final Connection conn, final String sql) throws Exception { Statement stmt = null; try { stmt = conn.createStatement(); stmt.execute(sql); } finally { JdbcUtils.closeStatement(stmt); } } /** * Execute statement trying do it in batch. */ private void executeStatementBatch(final Connection conn, final String sql) throws Exception { final Pattern pattern = Pattern.compile("DELETE FROM (\\w+) WHERE (\\w+) IN \\((.*)\\)"); final Matcher matcher = pattern.matcher(sql); if (matcher.matches()) { final String table = matcher.group(1); final String column = matcher.group(2); final String select = matcher.group(3); final List<Integer> ids = readIds(conn, select); deleteIdsInTableBatch(conn, ids, column, table); } else { // WHERE NOT IN query or some unknown ... -> just execute SQL. executeStatement(conn, sql); } } /** * gets array of primary keys that are to delete. used query cache. */ private List<Integer> readIds(final Connection conn, final String select) throws SQLException { List<Integer> ids = queryCache.get(select); if (ids == null) { ids = readIdsFromDB(conn, select); queryCache.put(select, ids); } return ids; } /** * gets array of primary keys that are to delete. read from database */ private List<Integer> readIdsFromDB(final Connection conn, final String select) throws SQLException { final List<Integer> ids = new ArrayList<Integer>(); Statement stmt = null; ResultSet rs = null; try { stmt = conn.createStatement(); rs = stmt.executeQuery(select); while (rs.next()) { ids.add(rs.getInt(1)); } } finally { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(stmt); } return ids; } /** * splits delete to batch */ private void deleteIdsInTableBatch(final Connection conn, final List<Integer> ids, final String column, final String table) throws Exception { int fromIndex, length; for (fromIndex = 0, length = ids.size(); fromIndex < length; fromIndex += BATCH_SIZE) { final int toIndex = fromIndex + BATCH_SIZE < length ? fromIndex + BATCH_SIZE : length; deleteIdsInTable(conn, ids.subList(fromIndex, toIndex), column, table); } } /** * deletes from table by idsds */ private void deleteIdsInTable(final Connection conn, final List<Integer> ids, final String column, final String table) throws Exception { final String join = StringUtils.join(ids, ","); final String sql = "DELETE FROM " + table + " WHERE " + column + " IN (" + join + ")"; executeStatement(conn, sql); } }