package org.jenkins.tools.test.dao; import com.google.appengine.api.datastore.*; import org.jenkins.tools.test.model.*; import java.util.*; import java.util.logging.Logger; /** * @author fcamblor */ public enum PluginCompatResultDAO { INSTANCE; public static interface CoreMatcher { Query enhanceSearchCoreQuery(Query query); Query enhanceSearchResultQuery(Query query, Map<Key, MavenCoordinates> coords); public static enum All implements CoreMatcher { INSTANCE; public Query enhanceSearchCoreQuery(Query query){ return query; } public Query enhanceSearchResultQuery(Query query, Map<Key, MavenCoordinates> coords){ return query; } } public static class Parameterized implements CoreMatcher { private List<MavenCoordinates> cores; public Parameterized(List<MavenCoordinates> cores){ this.cores = cores; } public Query enhanceSearchCoreQuery(Query query){ if(this.cores.size() == 0){ return query; } List<String> gavs = new ArrayList<String>(cores.size()); for(MavenCoordinates coord : cores){ gavs.add(coord.toGAV()); } return query.addFilter(Mappings.MavenCoordinatesProperties.gav.name(), Query.FilterOperator.IN, gavs); } public Query enhanceSearchResultQuery(Query query, Map<Key, MavenCoordinates> coords){ if(coords.size() == 0){ return query; } else { return query.addFilter(Mappings.PluginCompatResultProperties.coreCoordsKey.name(), Query.FilterOperator.IN, new ArrayList<Key>(coords.keySet())); } } } } public static interface PluginMatcher { public Query enhanceSearchPluginQuery(Query query); public Query enhanceSearchResultQuery(Query query, Map<Key, PluginInfos> pluginInfos); public static enum All implements PluginMatcher { INSTANCE; public Query enhanceSearchPluginQuery(Query query){ return query; } public Query enhanceSearchResultQuery(Query query, Map<Key, PluginInfos> pluginInfos){ return query; } } public static class Parameterized implements PluginMatcher { private List<String> pluginNames; public Parameterized(List<String> pluginNames){ this.pluginNames = pluginNames; } public Query enhanceSearchPluginQuery(Query query){ if(this.pluginNames.size() == 0){ return query; } return query.addFilter(Mappings.PluginInfosProperties.pluginName.name(), Query.FilterOperator.IN, pluginNames); } public Query enhanceSearchResultQuery(Query query, Map<Key, PluginInfos> pluginInfos){ if(pluginInfos.size() == 0){ return query; } else { return query.addFilter(Mappings.PluginCompatResultProperties.pluginInfosKey.name(), Query.FilterOperator.IN, new ArrayList<Key>(pluginInfos.keySet())); } } } } private static final Logger log = Logger.getLogger(PluginCompatResultDAO.class.getName()); public Key persist(PluginInfos pluginInfos, PluginCompatResult result){ Key coreCoordinatesEntityKey = createCoreCoordsIfNotExist(result.coreCoordinates); Key pluginInfosEntityKey = createPluginInfosIfNotExist(pluginInfos); Entity resultToPersist = Mappings.toEntity(result, result.coreCoordinates, coreCoordinatesEntityKey, pluginInfos, pluginInfosEntityKey); DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Key resultKey = datastore.put(resultToPersist); log.info("Plugin compat result stored with key : "+resultKey); return resultKey; } private List<Entity> executePaginatedQueries(DatastoreService service, Query query, int limit){ List<Entity> results = new ArrayList<Entity>(); PreparedQuery pq = service.prepare(query); QueryResultList<Entity> qrl = pq.asQueryResultList(FetchOptions.Builder.withLimit(limit)); results.addAll(qrl); log.finer("Retrieved "+qrl.size()+" results from query "+query.toString()); int pageNumber = 1; while(qrl.size() == limit){ log.finer("Retrieving page "+pageNumber+" for query "+query.toString()); Cursor cursor = qrl.getCursor(); qrl = pq.asQueryResultList(FetchOptions.Builder.withLimit(limit).startCursor(cursor)); results.addAll(qrl); log.finer("Retrieved "+qrl.size()+" results from query "+query.toString()); pageNumber++; } return results; } public PluginCompatReport search(PluginMatcher pluginMatcher, CoreMatcher coreMatcher){ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Query searchCoresQuery = new Query(Mappings.CORE_MAVEN_COORDS_KIND); searchCoresQuery = coreMatcher.enhanceSearchCoreQuery(searchCoresQuery); List<Entity> coreEntities = executePaginatedQueries(datastore, searchCoresQuery, 1000); Map<Key, MavenCoordinates> cores = Mappings.mavenCoordsFromEntity(coreEntities); Query searchPluginsQuery = new Query(Mappings.PluginInfosProperties.KIND); searchPluginsQuery = pluginMatcher.enhanceSearchPluginQuery(searchPluginsQuery); List<Entity> pluginInfoEntities = executePaginatedQueries(datastore, searchPluginsQuery, 1000); Map<Key, PluginInfos> pluginInfos = Mappings.pluginInfosFromEntity(pluginInfoEntities); Query searchResultsQuery = new Query(Mappings.PluginCompatResultProperties.KIND); if((pluginMatcher == PluginMatcher.All.INSTANCE && coreMatcher == CoreMatcher.All.INSTANCE) || (pluginMatcher == PluginMatcher.All.INSTANCE || coreMatcher == CoreMatcher.All.INSTANCE)){ coreMatcher.enhanceSearchResultQuery(searchResultsQuery, cores); pluginMatcher.enhanceSearchResultQuery(searchResultsQuery, pluginInfos); } else { // Yeah that's not really well object oriented... searchResultsQuery.addFilter(Mappings.PluginCompatResultProperties.computedCoreAndPlugin.name(), Query.FilterOperator.IN, cartesianProductOfCoreAndPlugins(cores, pluginInfos)); } List<Entity> results = executePaginatedQueries(datastore, searchResultsQuery, 1000); PluginCompatReport report = Mappings.pluginCompatReportFromResultsEntities(results, cores, pluginInfos); return report; } public SortedSet<MavenCoordinates> findAllCores(){ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Query searchCoresQuery = new Query(Mappings.CORE_MAVEN_COORDS_KIND); List<Entity> coreEntities = executePaginatedQueries(datastore, searchCoresQuery, 1000); Map<Key, MavenCoordinates> cores = Mappings.mavenCoordsFromEntity(coreEntities); return new TreeSet<MavenCoordinates>(cores.values()); } public SortedSet<String> findAllPluginInfoNames(){ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); Query searchPluginInfosQuery = new Query(Mappings.PluginInfosProperties.KIND); List<Entity> pluginInfosEntities = executePaginatedQueries(datastore, searchPluginInfosQuery, 1000); Map<Key, PluginInfos> pluginInfos = Mappings.pluginInfosFromEntity(pluginInfosEntities); SortedSet<String> names = new TreeSet<String>(new Comparator<String>() { public int compare(String s, String s1) { return s.compareToIgnoreCase(s1); } }); for(PluginInfos pi : pluginInfos.values()){ names.add(pi.pluginName); } return names; } public long purgeResults(){ DatastoreService datastoreService = DatastoreServiceFactory.getDatastoreService(); long deletedLines = 0; deletedLines += DAOUtils.purgeEntities(Mappings.CORE_MAVEN_COORDS_KIND); deletedLines += DAOUtils.purgeEntities(Mappings.PluginInfosProperties.KIND); deletedLines += DAOUtils.purgeEntities(Mappings.PluginCompatResultProperties.KIND); return deletedLines; } private List<String> cartesianProductOfCoreAndPlugins(Map<Key, MavenCoordinates> cores, Map<Key, PluginInfos> pluginInfos) { List<String> computedCoreAndPlugins = new ArrayList<String>(cores.values().size() * pluginInfos.values().size()); for(MavenCoordinates coords : cores.values()){ for(PluginInfos pi : pluginInfos.values()){ computedCoreAndPlugins.add(Mappings.computeCoreAndPlugin(coords, pi)); } } return computedCoreAndPlugins; } private Key createPluginInfosIfNotExist(PluginInfos pluginInfos) { DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); List<Entity> pluginInfosEntities = datastore.prepare( new Query(Mappings.PluginInfosProperties.KIND) .addFilter(Mappings.PluginInfosProperties.pluginName.name(), Query.FilterOperator.EQUAL, pluginInfos.pluginName) .addFilter(Mappings.PluginInfosProperties.pluginVersion.name(), Query.FilterOperator.EQUAL, pluginInfos.pluginVersion) .addFilter(Mappings.PluginInfosProperties.pluginUrl.name(), Query.FilterOperator.EQUAL, pluginInfos.pluginUrl) ).asList(FetchOptions.Builder.withDefaults()); Key result = null; if(pluginInfosEntities.size() == 0){ // Coordinate doesn't exist : let's create it ! Entity pluginInfoEntity = Mappings.toEntity(pluginInfos); result = datastore.put(pluginInfoEntity); } else { result = pluginInfosEntities.get(0).getKey(); } return result; } private Key createCoreCoordsIfNotExist(MavenCoordinates coreCoords){ return searchAndEventuallyCreateCoords(Mappings.CORE_MAVEN_COORDS_KIND, coreCoords); } private Key searchAndEventuallyCreateCoords(String kind, MavenCoordinates coords){ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); List<Entity> coordsEntities = datastore.prepare( new Query(kind) .addFilter(Mappings.MavenCoordinatesProperties.gav.name(), Query.FilterOperator.EQUAL, coords.toGAV()) ).asList(FetchOptions.Builder.withDefaults()); Key result = null; if(coordsEntities.size() == 0){ // Coordinate doesn't exist : let's create it ! Entity coordsEntity = Mappings.toEntity(coords, kind); result = datastore.put(coordsEntity); } else { result = coordsEntities.get(0).getKey(); } return result; } }