package org.activityinfo.legacy.shared.impl.pivot;
import com.bedatadriven.rebar.sql.client.SqlResultCallback;
import com.bedatadriven.rebar.sql.client.SqlResultSet;
import com.bedatadriven.rebar.sql.client.SqlResultSetRow;
import com.bedatadriven.rebar.sql.client.SqlTransaction;
import com.bedatadriven.rebar.sql.client.query.SqlQuery;
import com.google.common.collect.*;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.activityinfo.legacy.shared.command.DimensionType;
import org.activityinfo.legacy.shared.command.Filter;
import org.activityinfo.legacy.shared.command.GetSites;
import org.activityinfo.legacy.shared.command.PivotSites;
import org.activityinfo.legacy.shared.command.result.Bucket;
import org.activityinfo.legacy.shared.command.result.SiteResult;
import org.activityinfo.legacy.shared.impl.Tables;
import org.activityinfo.legacy.shared.impl.pivot.calc.*;
import org.activityinfo.legacy.shared.model.SiteDTO;
import org.activityinfo.legacy.shared.reports.content.DimensionCategory;
import org.activityinfo.legacy.shared.reports.content.EntityCategory;
import org.activityinfo.legacy.shared.reports.content.SimpleCategory;
import org.activityinfo.legacy.shared.reports.model.AdminDimension;
import org.activityinfo.legacy.shared.reports.model.AttributeGroupDimension;
import org.activityinfo.legacy.shared.reports.model.DateDimension;
import org.activityinfo.legacy.shared.reports.model.Dimension;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* Aggregates calculated indicators using the GetSites command.
*/
public class CalculatedIndicatorsQuery implements WorkItem {
private static final Logger LOGGER = Logger.getLogger(CalculatedIndicatorsQuery.class.getName());
private static final Dimension INDICATOR_DIM = new Dimension(DimensionType.Indicator);
private final PivotSites query;
private final PivotQueryContext queryContext;
private Set<Integer> activityIds = Sets.newHashSet();
private Set<Integer> indicatorIds = Sets.newHashSet();
private Map<Integer, DimensionCategory> activityMap = Maps.newHashMap();
private Map<Integer, DimensionCategory> activityCategoryMap = Maps.newHashMap();
private Map<Integer, DimensionCategory> activityToDatabaseMap = Maps.newHashMap();
private Map<Integer, EntityCategory> indicatorMap = Maps.newHashMap();
private Multimap<Integer, EntityCategory> attributes = HashMultimap.create();
private List<DimAccessor> dimAccessors = Lists.newArrayList();
private AsyncCallback<Void> callback;
public CalculatedIndicatorsQuery(PivotQueryContext queryContext) {
this.query = queryContext.getCommand();
this.queryContext = queryContext;
}
@Override
public void execute(final AsyncCallback<Void> callback) {
this.callback = callback;
final SqlQuery query = SqlQuery.selectDistinct()
.appendColumn("i.indicatorId", "indicatorId")
.appendColumn("i.name", "indicatorName")
.appendColumn("i.activityId", "activityId")
.appendColumn("i.sortOrder", "indicatorOrder")
.appendColumn("a.name", "activityName")
.appendColumn("a.category", "activityCategory")
.appendColumn("a.sortOrder", "activityOrder")
.appendColumn("db.DatabaseId", "databaseId")
.appendColumn("db.name", "databaseName")
.from(Tables.INDICATOR, "i")
.leftJoin(Tables.ACTIVITY, "a").on("a.activityId=i.activityId")
.leftJoin(Tables.USER_DATABASE, "db").on("a.databaseId=db.databaseId")
.whereTrue("i.calculatedAutomatically=1 and i.Expression is not null");
Filter filter = this.query.getFilter();
if(filter.isRestricted(DimensionType.Indicator)) {
query.where("i.indicatorId").in(filter.getRestrictions(DimensionType.Indicator));
} else if(filter.isRestricted(DimensionType.Activity)) {
query.where("i.activityId").in(filter.getRestrictions(DimensionType.Activity));
} else if(filter.isRestricted(DimensionType.Database)) {
query.where("a.databaseId").in(filter.getRestrictions(DimensionType.Database));
} else {
// too broad
callback.onSuccess(null);
return;
}
// enforce visibility rules
query.whereTrue(visibilityRules());
query.execute(queryContext.getExecutionContext().getTransaction(), new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
if (results.getRows().isEmpty()) {
callback.onSuccess(null);
} else {
for (SqlResultSetRow row : results.getRows()) {
LOGGER.info("row = " + row);
int activityId = row.getInt("activityId");
int indicatorId = row.getInt("indicatorId");
activityIds.add(activityId);
indicatorIds.add(indicatorId);
activityMap.put(activityId, new EntityCategory(activityId,
row.getString("activityName"),
row.getInt("activityOrder")));
activityCategoryMap.put(activityId, new SimpleCategory(row.getString("activityCategory")));
activityToDatabaseMap.put(activityId,
new EntityCategory(row.getInt("databaseId"), row.getString("databaseName")));
indicatorMap.put(indicatorId,
new EntityCategory(indicatorId,
row.getString("indicatorName"),
row.getInt("indicatorOrder")));
}
if (queryContext.getCommand().isPivotedBy(DimensionType.AttributeGroup)) {
queryAttributeGroups();
} else {
querySites();
}
}
}
});
}
private void queryAttributeGroups() {
Set<Integer> groupIds = Sets.newHashSet();
for(Dimension dim : query.getDimensions()) {
if(dim instanceof AttributeGroupDimension) {
AttributeGroupDimension groupDim = (AttributeGroupDimension) dim;
groupIds.add(groupDim.getAttributeGroupId());
}
}
SqlQuery.select()
.appendColumn("g.attributeGroupId", "groupId")
.appendColumn("g.name", "groupName")
.appendColumn("a.attributeId")
.appendColumn("a.name")
.appendColumn("a.sortOrder")
.from(Tables.ATTRIBUTE, "a")
.leftJoin(Tables.ATTRIBUTE_GROUP, "g").on("a.attributeGroupId=g.attributeGroupId")
.where("a.attributeGroupId").in(groupIds)
.execute(queryContext.getExecutionContext().getTransaction(), new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
for (SqlResultSetRow row : results.getRows()) {
int groupId = row.getInt("groupId");
int attributeId = row.getInt("attributeId");
int sortOrder = row.getInt("sortOrder");
String attributeName = row.getString("name");
attributes.put(groupId, new EntityCategory(attributeId, attributeName, sortOrder));
}
querySites();
}
});
}
private String visibilityRules() {
int userId = queryContext.getExecutionContext().getUser().getId();
return new StringBuilder()
.append("(")
// databases we own
.append("db.OwnerUserId = ").append(userId).append(" ")
// databases with allow view all
.append("OR ")
.append("db.DatabaseId IN (")
.append(" SELECT ")
.append(" p.DatabaseId ")
.append(" FROM ")
.append(" userpermission p ")
.append(" WHERE ")
.append(" p.UserId = ")
.append(userId)
.append(" AND p.AllowViewAll").append(") ")
// or activities that are published
.append(" OR ")
.append(" (a.published > 0)")
.append(")")
.toString();
}
private void querySites() {
GetSites sitesQuery = new GetSites(composeSiteFilter());
sitesQuery.setFetchAdminEntities( query.isPivotedBy(DimensionType.AdminLevel) );
sitesQuery.setFetchAttributes(query.isPivotedBy(DimensionType.AttributeGroup));
sitesQuery.setFetchAllIndicators(true);
sitesQuery.setFetchLocation(query.isPivotedBy(DimensionType.Location));
sitesQuery.setFetchPartner(query.isPivotedBy(DimensionType.Partner));
sitesQuery.setFetchComments(false);
sitesQuery.setFetchDates(query.isPivotedBy(DimensionType.Date));
sitesQuery.setFetchLinks(false);
sitesQuery.setFetchAllReportingPeriods(true);
sitesQuery.setLimit(-1);
for (Dimension dim : query.getDimensions()) {
if (dim.getType() != DimensionType.Indicator) {
dimAccessors.add(createAccessor(dim));
}
}
queryContext.getExecutionContext().execute(sitesQuery, new AsyncCallback<SiteResult>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
@Override
public void onSuccess(SiteResult result) {
try {
aggregateSites(result);
callback.onSuccess(null);
} catch(Throwable caught) {
callback.onFailure(caught);
}
}
});
}
private Filter composeSiteFilter() {
Filter siteFilter = new Filter();
siteFilter.addRestriction(DimensionType.Activity, activityIds);
for(DimensionType type : query.getFilter().getRestrictedDimensions()) {
if(type != DimensionType.Activity && type != DimensionType.Database && type != DimensionType.Indicator) {
siteFilter.addRestriction(type, query.getFilter().getRestrictions(type));
}
}
return siteFilter;
}
private DimAccessor createAccessor(Dimension dim) {
if(dim.getType() == DimensionType.Activity) {
return new ActivityAccessor(dim, activityMap);
} else if(dim.getType() == DimensionType.ActivityCategory) {
return new ActivityAccessor(dim, activityCategoryMap);
} else if(dim.getType() == DimensionType.Database) {
return new ActivityAccessor(dim, activityToDatabaseMap);
} else if(dim.getType() == DimensionType.AdminLevel) {
return new AdminAccessor((AdminDimension) dim);
} else if(dim.getType() == DimensionType.Date) {
DateDimension dateDim = (DateDimension) dim;
return new DateAccessor(dateDim);
} else if(dim.getType() == DimensionType.AttributeGroup) {
AttributeGroupDimension groupDim = (AttributeGroupDimension) dim;
return new AttributeAccessor(groupDim, attributes.get(groupDim.getAttributeGroupId()));
} else if(dim.getType() == DimensionType.Location) {
return new LocationAccessor(dim);
} else if (dim.getType() == DimensionType.Partner) {
return new PartnerAccessor(dim);
} else if (dim.getType() == DimensionType.Site) {
return new SiteAccessor(dim);
} else if (dim.getType() == DimensionType.Target) {
return new TargetAccessor(dim);
}
throw new UnsupportedOperationException("dim: " + dim);
}
private void aggregateSites(SiteResult result) {
Map<BucketKey, Bucket> buckets = Maps.newHashMap();
for(int i=0;i!=result.getTotalLength();++i) {
SiteDTO site = result.getData().get(i);
// These dimensions apply to the site as a whole
DimensionCategory siteDims[] = new DimensionCategory[dimAccessors.size()];
for (int j = 0; j != dimAccessors.size(); ++j) {
siteDims[j] = dimAccessors.get(j).getCategory(site);
}
// Now loop over each value
for(EntityCategory indicator : indicatorMap.values()) {
Double value = site.getIndicatorDoubleValue(indicator.getId());
if(value != null) {
BucketKey key = new BucketKey(indicator, siteDims);
Bucket bucket = buckets.get(key);
if (bucket == null) {
bucket = new Bucket();
bucket.setCategory(INDICATOR_DIM, indicator);
for (int j = 0; j != dimAccessors.size(); ++j) {
bucket.setCategory(dimAccessors.get(j).getDimension(), siteDims[j]);
}
buckets.put(key, bucket);
queryContext.addBucket(bucket);
}
bucket.appendValue(value);
}
}
}
}
}