package org.fluxtream.core.services.impl; import org.fluxtream.core.aspects.FlxLogger; import org.fluxtream.core.connectors.annotations.Updater; import org.fluxtream.core.domain.AbstractFacet; import org.fluxtream.core.services.JPADaoService; import org.fluxtream.core.utils.JPAUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import javax.persistence.Entity; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import java.math.BigInteger; import java.util.List; import java.util.Set; /** * User: candide * Date: 18/08/14 * Time: 20:32 */ @Service public class ApiDataCleanupServiceImpl implements ApiDataCleanupService { @Autowired @Qualifier("txTemplate") TransactionTemplate transactionTemplate; @Autowired @Qualifier("jdbcTemplate") JdbcTemplate jdbcTemplate; @PersistenceContext EntityManager em; @Autowired JPADaoService jpaDaoService; @Override public void cleanupStaleData() throws Exception { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class)); Set<BeanDefinition> components = scanner.findCandidateComponents("org.fluxtream"); for (BeanDefinition component : components) { Class cls = Class.forName(component.getBeanClassName()); Updater updaterAnnotation = getUpdaterForFacet(cls); if (updaterAnnotation==null) continue; final String entityName = JPAUtils.getEntityName(cls); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info("Cleaning up entity: " + entityName + "..."); if (entityName.startsWith("Facet_")) { if (!JPAUtils.hasRelation(cls)) { // Clean up entries for apiKeyId's which are no longer present in the system, but preserve items with // api=0 to preserve the locations generated from reverse IP lookup when the users log in. cleanupStaleFacetsInBulk(entityName, updaterAnnotation.value()); } else { cleanupStaleFacetEntities(cls, entityName); } } } // final int i = jdbcTemplate.update("DELETE FROM ApiUpdates WHERE apiKeyId NOT IN (" + join(getAllApiKeyIds()) + ");"); // StringBuilder sb = new StringBuilder("ApiUpdates cleaned up, facetsDeleted: ").append(i); // FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info(sb.toString()); } private Updater getUpdaterForFacet(Class cls) throws ClassNotFoundException { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false); scanner.addIncludeFilter(new AnnotationTypeFilter(Updater.class)); Set<BeanDefinition> components = scanner.findCandidateComponents(cls.getPackage().getName()); if (!components.isEmpty()) return Class.forName(components.iterator().next().getBeanClassName()).getAnnotation(Updater.class); return null; } private void cleanupStaleFacetEntities(final Class cls, final String entityName) { final String sqlString = "SELECT * FROM " + entityName + " WHERE (apiKeyId NOT IN (SELECT DISTINCT id from ApiKey)) AND api!=0;"; Query query = em.createNativeQuery(sqlString, cls); final String txIsolation = (String) em.createNativeQuery("SELECT @@tx_isolation").getSingleResult(); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info("txManager isolation: " + txIsolation); final List<? extends AbstractFacet> facetsToDelete = query.getResultList(); final int totalFacetsToDelete = facetsToDelete.size(); int deleteCount = 0; if (totalFacetsToDelete > 0) { for (final AbstractFacet facet : facetsToDelete) { try { jpaDaoService.deleteFacet(facet); deleteCount++; } catch (Throwable t) { FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").warn("Couldn't delete facet, entityName: " + entityName + ", id: " + facet.getId()); } } StringBuilder sb = new StringBuilder("JPA-Cleaned up entity class \"" + entityName + "\", facetsDeleted=").append(deleteCount + "/" + totalFacetsToDelete + " in total"); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info(sb.toString()); } } private void cleanupStaleFacetsInBulk(final String entityName, final int apiCode) { final List<BigInteger> allApiKeyIds = getConnectorApiKeyIds(apiCode); if (entityName.equals("Facet_Location")) { // optimize for existing indexes on the locations table for which the default query would talk too long Query locationApiKeyIdsQuery = em.createNativeQuery("SELECT DISTINCT apiKeyId FROM Facet_Location"); List<BigInteger> locationApiKeyIds = locationApiKeyIdsQuery.getResultList(); for (final BigInteger locationApiKeyId : locationApiKeyIds) { if (!allApiKeyIds.contains(locationApiKeyId)){ transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { try { final String txIsolation = jdbcTemplate.queryForObject("SELECT @@tx_isolation", String.class); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info("txManager isolation: " + txIsolation); final int i = jdbcTemplate.update("DELETE FROM " + entityName + " WHERE apiKeyId=" + locationApiKeyId); StringBuilder sb = new StringBuilder("Cleaned invalid location entries, facetsDeleted=").append(i); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info(sb.toString()); } catch (Throwable e) { FlxLogger.getLogger("org.fluxtream.core.updaters.quartz") .warn("Couldn't bulk delete entities, entityName: " + entityName + " ( " + e.getMessage() + ")"); } } }); } } } else { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { try { final String txIsolation = jdbcTemplate.queryForObject("SELECT @@tx_isolation", String.class); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info("txManager isolation: " + txIsolation); final int i = jdbcTemplate.update("DELETE FROM " + entityName + " WHERE (apiKeyId NOT IN (" + join(allApiKeyIds) + ")) AND api!=0;"); StringBuilder sb = new StringBuilder("Bulk cleaned up entity \"" + entityName + "\", facetsDeleted=").append(i); FlxLogger.getLogger("org.fluxtream.core.updaters.quartz").info(sb.toString()); } catch (Throwable e) { FlxLogger.getLogger("org.fluxtream.core.updaters.quartz") .warn("Couldn't bulk delete entities, entityName: " + entityName + " ( " + e.getMessage() + ")"); } } }); } } private List<BigInteger> getConnectorApiKeyIds(int apiCode) { Query allApiKeyIdsQuery = em.createNativeQuery("SELECT id FROM ApiKey apiKey WHERE api=" + apiCode); return allApiKeyIdsQuery.getResultList(); } private List<BigInteger> getAllApiKeyIds() { Query allApiKeyIdsQuery = em.createNativeQuery("SELECT id FROM ApiKey"); return allApiKeyIdsQuery.getResultList(); } private String join(List<BigInteger> apiKeyIds) { int index = 0; StringBuffer sb = new StringBuffer(); for (BigInteger apiKeyId : apiKeyIds) { if (index>0) sb.append(","); sb.append(String.valueOf(apiKeyId)); index++; } return sb.toString(); } }