/* * Copyright (C) 2010-2012 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * * Akvo FLOW is free software: you can redistribute it and modify it under the terms of * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, * either version 3 of the License or any later version. * * Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Affero General Public License included below for more details. * * The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>. */ package org.waterforpeople.mapping.analytics.dao; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import javax.jdo.PersistenceManager; import org.waterforpeople.mapping.analytics.domain.AccessPointMetricSummary; import com.gallatinsystems.framework.dao.BaseDAO; import com.gallatinsystems.framework.servlet.PersistenceFilter; import com.gallatinsystems.gis.map.dao.OGRFeatureDao; import com.gallatinsystems.gis.map.domain.OGRFeature; /** * Dao for manipulating access point summary domain objects * * @author Christopher Fagiani */ public class AccessPointMetricSummaryDao extends BaseDAO<AccessPointMetricSummary> { private static final int NUM_SHARDS = 11; public AccessPointMetricSummaryDao() { super(AccessPointMetricSummary.class); } /** * lists metrics that match the prototype passed in. The object passed in must have at least 1 * field populated (besides count). In practice, callers should populate as many fields as * possible to narrow results. This will collapse any shards and objects returned will not have * any keys (since they are transient roll-up objects) * * @param prototype * @return */ public List<AccessPointMetricSummary> listMetrics( AccessPointMetricSummary prototype) { return listMetrics(prototype, true); } /** * lists metrics that match the prototype passed in. The object passed in must have at least 1 * field populated (besides count). In practice, callers should populate as many fields as * possible to narrow results. This will collapse any shards and objects returned will not have * any keys (since they are transient roll-up objects) if fetchCentroid is true, the summaries * returned will have their centroid lat/lon popuated using the OGRFeature that corresponds to * the sublevel. Setting this to true will degrade performance when listing a large number of * summaries. * * @param prototype * @param fetchCentroid * @return */ public List<AccessPointMetricSummary> listMetrics( AccessPointMetricSummary prototype, boolean fetchCentroid) { List<AccessPointMetricSummary> summaries = listMetrics(prototype, null); Map<String, AccessPointMetricSummary> rollups = new HashMap<String, AccessPointMetricSummary>(); if (summaries != null) { for (AccessPointMetricSummary s : summaries) { AccessPointMetricSummary rollup = rollups.get(s .identifierString()); if (rollup == null) { rollup = new AccessPointMetricSummary(); rollup.setCount((s.getCount() != null ? s.getCount() : 0)); rollup.setCountry(s.getCountry()); rollup.setLastUpdateDateTime(s.getLastUpdateDateTime()); rollup.setMetricGroup(s.getMetricGroup()); rollup.setMetricName(s.getMetricName()); rollup.setMetricValue(s.getMetricValue()); rollup.setOrganization(s.getOrganization()); rollup.setSubLevel(s.getSubLevel()); rollup.setSubLevelName(s.getSubLevelName()); rollup.setSubValue(s.getSubValue()); rollup.setPeriodType(s.getPeriodType()); rollup.setPeriodValue(s.getPeriodValue()); rollups.put(s.identifierString(), rollup); rollup.setParentSubName(s.getParentSubName()); } else { rollup.setCount(rollup.getCount() + (s.getCount() != null ? s.getCount() : 0)); } } } List<AccessPointMetricSummary> rollupList = new ArrayList<AccessPointMetricSummary>(); rollupList.addAll(rollups.values()); if (fetchCentroid) { // TODO: this shouldn't be in the Dao. Ask dru why he needs this OGRFeatureDao ogrFeatureDao = new OGRFeatureDao(); for (AccessPointMetricSummary item : rollupList) { if (item.getSubValue() != null) { List<OGRFeature> ogr = ogrFeatureDao .listBySubLevelCountryName(item.getCountry(), prototype.getSubLevel(), item.getSubValue(), "all", item.getParentSubName()); for (OGRFeature ogrItem : ogr) { item.setLatitude(ogrItem.getCentroidLat()); item.setLongitude(ogrItem.getCentroidLon()); } } } } return rollupList; } /** * gets the metric matching the prototype passed in with a specific shard number * * @param prototype * @param shardNum * @return */ @SuppressWarnings("unchecked") public List<AccessPointMetricSummary> listMetrics( AccessPointMetricSummary prototype, Integer shardNum) { Map<String, Object> paramMap = new HashMap<String, Object>(); StringBuilder filterString = new StringBuilder(); StringBuilder paramString = new StringBuilder(); appendNonNullParam("organization", filterString, paramString, "String", prototype.getOrganization(), paramMap); appendNonNullParam("country", filterString, paramString, "String", prototype.getCountry(), paramMap); appendNonNullParam("subLevel", filterString, paramString, "Integer", prototype.getSubLevel(), paramMap); appendNonNullParam("subValue", filterString, paramString, "String", prototype.getSubValue(), paramMap); appendNonNullParam("metricName", filterString, paramString, "String", prototype.getMetricName(), paramMap); appendNonNullParam("metricGroup", filterString, paramString, "String", prototype.getMetricGroup(), paramMap); appendNonNullParam("metricValue", filterString, paramString, "String", prototype.getMetricValue(), paramMap); appendNonNullParam("shardNum", filterString, paramString, "Integer", shardNum, paramMap); PersistenceManager pm = PersistenceFilter.getManager(); javax.jdo.Query query = pm.newQuery(AccessPointMetricSummary.class); query.setFilter(filterString.toString()); query.declareParameters(paramString.toString()); return (List<AccessPointMetricSummary>) query.executeWithMap(paramMap); } /** * synchronized static method so that only 1 thread can be updating a summary at a time. This is * inefficient but is the only way we can be sure we're keeping the count consistent since there * is no "select for update" or sql dml-like construct. When this method is called, the metric * object passed in must have its value populated as well as at LEAST the organization and * country. * * @param answer */ public static synchronized void incrementCount( AccessPointMetricSummary metric, int unit) { AccessPointMetricSummaryDao dao = new AccessPointMetricSummaryDao(); Random generator = new Random(); int shardNum = generator.nextInt(NUM_SHARDS); List<AccessPointMetricSummary> results = dao.listMetrics(metric, shardNum); AccessPointMetricSummary summary = null; if ((results == null || results.size() == 0) && unit > 0) { metric.setCount(new Long(unit)); metric.setShardNum(shardNum); summary = metric; } else if (results != null && results.size() > 0) { summary = (AccessPointMetricSummary) results.get(0); summary.setCount(summary.getCount() + unit); } if (summary != null) { if (summary.getCount() > 0) { dao.save(summary); } else if (summary.getKey() != null) { // if count has been decremented to 0 and the object is // already persisted, delete it dao.delete(summary); } } } }