package org.activityinfo.legacy.shared.impl;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
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.bedatadriven.rebar.sql.client.util.RowHandler;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.collect.Lists;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.activityinfo.legacy.shared.command.GetActivityForm;
import org.activityinfo.legacy.shared.exception.IllegalAccessCommandException;
import org.activityinfo.legacy.shared.model.*;
import org.activityinfo.legacy.shared.reports.util.mapping.Extents;
import org.activityinfo.promise.Promise;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GetActivityFormHandler implements CommandHandlerAsync<GetActivityForm, ActivityFormDTO> {
private ActivityFormCache cache;
public GetActivityFormHandler() {
this.cache = new ActivityFormCache();
}
public GetActivityFormHandler(ActivityFormCache cache) {
this.cache = cache;
}
@Override
public void execute(GetActivityForm command, final ExecutionContext context, AsyncCallback<ActivityFormDTO> callback) {
Promise<ActivityFormDTO> form = fetchForm(context, command.getActivityId());
// Apply permissions independently of caching so that we can safely cache the form for all users.
form.join(new Function<ActivityFormDTO, Promise<ActivityFormDTO>>() {
@Nullable
@Override
public Promise<ActivityFormDTO> apply(ActivityFormDTO form) {
if (form.getOwnerUserId() == context.getUser().getId()) {
form.setEditAllowed(true);
form.setEditAllAllowed(true);
form.setDesignAllowed(true);
return Promise.resolved(form);
} else {
return applyPermissions(context, form);
}
}
}).then(callback);
}
private Promise<ActivityFormDTO> applyPermissions(ExecutionContext context, final ActivityFormDTO form) {
final Promise<ActivityFormDTO> result = new Promise<>();
SqlQuery.selectAll()
.appendColumn("allowView")
.appendColumn("allowViewAll")
.appendColumn("allowEdit")
.appendColumn("allowEditAll")
.appendColumn("allowDesign")
.appendColumn("partnerId")
.from(Tables.USER_PERMISSION, "p")
.where("p.UserId").equalTo(context.getUser().getId())
.where("p.DatabaseId").equalTo(form.getDatabaseId())
.execute(context.getTransaction(), new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
if(results.getRows().isEmpty()) {
result.reject(new IllegalAccessCommandException());
return;
}
SqlResultSetRow row = results.getRow(0);
if(!row.getBoolean("allowView")) {
result.reject(new IllegalAccessCommandException());
return;
}
form.setEditAllowed(row.getBoolean("allowEdit"));
form.setEditAllAllowed(row.getBoolean("allowEditAll"));
form.setDesignAllowed(row.getBoolean("allowDesign"));
form.setCurrentPartnerId(row.getInt("partnerId"));
result.resolve(form);
}
});
return result;
}
private Promise<ActivityFormDTO> fetchForm(ExecutionContext context, final int activityId) {
ActivityFormDTO form = cache.getCopyIfPresent(activityId);
if(form != null) {
return Promise.resolved(form);
}
return new FormBuilder(activityId).build(context).then(new Function<ActivityFormDTO, ActivityFormDTO>() {
@Override
public ActivityFormDTO apply(ActivityFormDTO input) {
cache.put(input);
return input;
}
});
}
private class FormBuilder {
private int countryId;
private int databaseId;
private int activityId;
private ActivityFormDTO activity;
private final Map<Integer, AttributeGroupDTO> attributeGroups = new HashMap<Integer, AttributeGroupDTO>();
private SqlTransaction tx;
private FormBuilder(int activityId) {
this.activityId = activityId;
this.activity = new ActivityFormDTO();
this.activity.setId(activityId);
}
public Promise<ActivityFormDTO> build(ExecutionContext context) {
this.tx = context.getTransaction();
List<Promise<Void>> tasks = Lists.newArrayList();
tasks.add(loadActivity());
tasks.add(loadIndicators());
tasks.add(loadLockedPeriods());
tasks.add(loadAttributeGroups());
tasks.add(loadAttributes());
return Promise.waitAll(tasks).then(Functions.constant(activity));
}
public Promise<Void> loadActivity() {
SqlQuery query = SqlQuery.select()
.appendColumn("a.activityId", "activityId")
.appendColumn("a.name", "name")
.appendColumn("a.category", "category")
.appendColumn("a.locationTypeId", "locationTypeId")
.appendColumn("t.name", "locationTypeName")
.appendColumn("t.boundAdminLevelId", "locationTypeLevelId")
.appendColumn("t.databaseId", "locationTypeDatabaseId")
.appendColumn("t.workflowId", "locationTypeWorkflowId")
.appendColumn("a.reportingFrequency", "reportingFrequency")
.appendColumn("a.databaseId", "databaseId")
.appendColumn("a.classicView", "classicView")
.appendColumn("a.published", "published")
.appendColumn("db.name", "databaseName")
.appendColumn("db.ownerUserId", "ownerUserId")
.appendColumn("c.countryId", "countryId")
.appendColumn("c.x1", "x1")
.appendColumn("c.y1", "y1")
.appendColumn("c.x2", "x2")
.appendColumn("c.y2", "y2")
.from("activity", "a")
.leftJoin("userdatabase", "db").on("db.databaseId=a.databaseId")
.leftJoin("country", "c").on("db.countryId=c.countryId")
.leftJoin("locationtype", "t").on("t.locationTypeId=a.locationTypeId")
.where("a.activityId").equalTo(activityId);
return execute(query, new RowHandler() {
@Override
public void handleRow(SqlResultSetRow row) {
countryId = row.getInt("countryId");
databaseId = row.getInt("databaseId");
Extents countryBounds = new Extents(
row.getDouble("x1"),
row.getDouble("y1"),
row.getDouble("x2"),
row.getDouble("y2"));
LocationTypeDTO locationType = new LocationTypeDTO();
locationType.setId(row.getInt("locationTypeId"));
locationType.setName(row.getString("locationTypeName"));
locationType.setCountryBounds(countryBounds);
locationType.setWorkflowId(row.getString("locationTypeWorkflowId"));
if(!row.isNull("locationTypeLevelId")) {
locationType.setBoundAdminLevelId(row.getInt("locationTypeLevelId"));
}
if(!row.isNull("locationTypeDatabaseId")) {
locationType.setDatabaseId(row.getInt("locationTypeDatabaseId"));
}
activity.setId(row.getInt("activityId"));
activity.setDatabaseId(databaseId);
activity.setDatabaseName(row.getString("databaseName"));
activity.setOwnerUserId(row.getInt("ownerUserId"));
activity.setName(row.getString("name"));
activity.setCategory(row.getString("category"));
activity.setReportingFrequency(row.getInt("reportingFrequency"));
activity.setPublished(row.getInt("published"));
activity.setClassicView(row.getBoolean("classicView"));
activity.setLocationType(locationType);
}
}).join(new Function<Void, Promise<Void>>() {
@Nullable
@Override
public Promise<Void> apply(Void input) {
return Promise.waitAll(
loadAdminLevels(),
loadPartners(),
loadProjects());
}
});
}
public Promise<Void> loadAdminLevels() {
return execute(SqlQuery.select("adminLevelId", "name", "parentId", "countryId")
.from("adminlevel")
.whereTrue("deleted=0")
.where("countryId").equalTo(countryId),
new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
Map<Integer, AdminLevelDTO> levels = new HashMap<Integer, AdminLevelDTO>();
for(SqlResultSetRow row : results.getRows()) {
AdminLevelDTO level = new AdminLevelDTO();
level.setId(row.getInt("adminLevelId"));
level.setName(row.getString("name"));
level.setCountryId(row.getInt("countryId"));
if (!row.isNull("parentId")) {
level.setParentLevelId(row.getInt("parentId"));
}
levels.put(level.getId(), level);
}
activity.getLocationType().setAdminLevels(levelsForLocationType(levels, activity.getLocationType()));
}
});
}
private List<AdminLevelDTO> levelsForLocationType(Map<Integer, AdminLevelDTO> adminLevels, LocationTypeDTO type) {
if (type.isAdminLevel()) {
// if this activity is bound to an administrative
// level, then we need only as far down as this goes
return getRootAdminLevel(adminLevels, type);
} else if(type.isNationwide()) {
return Lists.newArrayList();
} else {
return new ArrayList<>(adminLevels.values());
}
}
/**
* Must be called only by location type that has admin level bound!
* @param type location type
* @return root admin level
*/
private List<AdminLevelDTO> getRootAdminLevel(Map<Integer, AdminLevelDTO> adminLevels, LocationTypeDTO type) {
List<AdminLevelDTO> ancestors = new ArrayList<AdminLevelDTO>();
AdminLevelDTO level = adminLevels.get(type.getBoundAdminLevelId());
if (level == null) {
throw new IllegalStateException("Unable to find any admin level however in locationtype is marked to bound to admin level.");
}
while (true) {
ancestors.add(0, level);
if (level.isRoot()) {
return ancestors;
} else {
level = adminLevels.get(level.getParentLevelId());
}
}
}
protected Promise<Void> loadProjects() {
final Promise<Void> promise = new Promise<>();
SqlQuery.select("name", "projectId", "description", "databaseId")
.from("project")
.where("databaseId").equalTo(databaseId)
.where("dateDeleted").isNull()
.orderBy("name")
.execute(tx, new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
List<ProjectDTO> projects = Lists.newArrayList();
for (SqlResultSetRow row : results.getRows()) {
ProjectDTO project = new ProjectDTO();
project.setName(row.getString("name"));
project.setId(row.getInt("projectId"));
project.setDescription(row.getString("description"));
projects.add(project);
}
activity.setProjects(projects);
promise.resolve(null);
}
});
return promise;
}
protected Promise<Void> loadLockedPeriods() {
final Promise<Void> promise = new Promise<>();
SqlQuery.select("fromDate",
"toDate",
"enabled",
"name",
"lockedPeriodId",
"userDatabaseId",
"activityId",
"projectId")
.from("lockedperiod")
.whereTrue("(userDatabaseId=" + databaseId + ") OR " +
"(activityId=" + activity.getId() + ") OR " +
"(projectId in (select projectId from project where databaseId=" + databaseId + "))")
.execute(tx, new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
for (SqlResultSetRow row : results.getRows()) {
LockedPeriodDTO lockedPeriod = new LockedPeriodDTO();
lockedPeriod.setId(row.getInt("lockedPeriodId"));
lockedPeriod.setFromDate(row.getDate("fromDate"));
lockedPeriod.setToDate(row.getDate("toDate"));
lockedPeriod.setEnabled(row.getBoolean("enabled"));
lockedPeriod.setName(row.getString("name"));
if(!row.isNull("userDatabaseId")) {
lockedPeriod.setParentId(row.getInt("userDatabaseId"));
lockedPeriod.setParentType(UserDatabaseDTO.ENTITY_NAME);
activity.getLockedPeriods().add(lockedPeriod);
} else if(!row.isNull("activityId")) {
lockedPeriod.setParentId(row.getInt("activityId"));
lockedPeriod.setParentType(ActivityDTO.ENTITY_NAME);
activity.getLockedPeriods().add(lockedPeriod);
} else if(!row.isNull("projectId")) {
lockedPeriod.setParentId(row.getInt("projectId"));
lockedPeriod.setParentType(ProjectDTO.ENTITY_NAME);
activity.getLockedPeriods().add(lockedPeriod);
}
}
promise.resolve(null);
}
});
return promise;
}
private Promise<Void> loadPartners() {
SqlQuery query = SqlQuery.select("d.databaseId", "d.partnerId", "p.name", "p.fullName")
.from(Tables.PARTNER_IN_DATABASE, "d")
.leftJoin(Tables.PARTNER, "p")
.on("d.PartnerId = p.PartnerId")
.where("d.databaseid").equalTo(databaseId)
.orderBy("p.name");
return execute(query, new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
List<PartnerDTO> partners = Lists.newArrayList();
for(SqlResultSetRow row : results.getRows()) {
PartnerDTO partner = new PartnerDTO();
partner.setId(row.getInt("partnerId"));
partner.setName(row.getString("name"));
partner.setFullName(row.getString("fullName"));
partners.add(partner);
}
activity.setPartnerRange(partners);
}
});
}
public Promise<Void> loadIndicators() {
SqlQuery query = SqlQuery.select("indicatorId",
"name",
"type",
"expression",
"skipExpression",
"nameInExpression",
"calculatedAutomatically",
"category",
"listHeader",
"description",
"aggregation",
"units",
"activityId",
"sortOrder",
"mandatory")
.from("indicator")
.where("activityId").equalTo(activity.getId())
.whereTrue("dateDeleted is null")
.orderBy("SortOrder");
return execute(query, new RowHandler() {
@Override
public void handleRow(SqlResultSetRow rs) {
IndicatorDTO indicator = new IndicatorDTO();
indicator.setId(rs.getInt("indicatorId"));
indicator.setName(rs.getString("name"));
indicator.setTypeId(rs.getString("type"));
indicator.setExpression(rs.getString("expression"));
indicator.setSkipExpression(rs.getString("skipExpression"));
indicator.setNameInExpression(rs.getString("nameInExpression"));
indicator.setCalculatedAutomatically(rs.getBoolean("calculatedAutomatically"));
indicator.setCategory(rs.getString("category"));
indicator.setListHeader(rs.getString("listHeader"));
indicator.setDescription(rs.getString("description"));
indicator.setAggregation(rs.getInt("aggregation"));
indicator.setUnits(rs.getString("units"));
indicator.setMandatory(rs.getBoolean("mandatory"));
indicator.setSortOrder(rs.getInt("sortOrder"));
activity.getIndicators().add(indicator);
}
});
}
public Promise<Void> loadAttributeGroups() {
SqlQuery query = SqlQuery.select()
.appendColumn("AttributeGroupId", "id")
.appendColumn("Name", "name")
.appendColumn("multipleAllowed")
.appendColumn("mandatory")
.appendColumn("defaultValue")
.appendColumn("workflow")
.appendColumn("sortOrder")
.from("attributegroup")
.where("attributeGroupId").in(attributeGroups())
.whereTrue("dateDeleted is NULL")
.orderBy("SortOrder");
return execute(query, new RowHandler() {
@Override
public void handleRow(SqlResultSetRow rs) {
AttributeGroupDTO group = new AttributeGroupDTO();
group.setId(rs.getInt("id"));
group.setName(rs.getString("name"));
group.setMultipleAllowed(rs.getBoolean("multipleAllowed"));
group.setMandatory(rs.getBoolean("mandatory"));
group.setSortOrder(rs.getInt("sortOrder"));
if (!rs.isNull("defaultValue")) { // if null it throws NPE
group.setDefaultValue(rs.getInt("defaultValue"));
}
group.setWorkflow(rs.getBoolean("workflow"));
attributeGroups.put(group.getId(), group);
activity.getAttributeGroups().add(group);
}
});
}
public Promise<Void> loadAttributes() {
SqlQuery query = SqlQuery.select("attributeId", "name", "attributeGroupId")
.from("attribute")
.orderBy("SortOrder")
.where("attributeGroupId").in(attributeGroups())
.whereTrue("dateDeleted is null");
return execute(query, new RowHandler() {
@Override
public void handleRow(SqlResultSetRow row) {
AttributeDTO attribute = new AttributeDTO();
attribute.setId(row.getInt("attributeId"));
attribute.setName(row.getString("name"));
int groupId = row.getInt("attributeGroupId");
AttributeGroupDTO group = attributeGroups.get(groupId);
if (group != null) {
group.getAttributes().add(attribute);
}
}
});
}
private SqlQuery attributeGroups() {
return SqlQuery.select("AttributeGroupId")
.from("attributegroupinactivity")
.where("ActivityId").equalTo(activity.getId());
}
private Promise<Void> execute(SqlQuery query, final SqlResultCallback rowHandler) {
final Promise<Void> promise = new Promise<>();
query.execute(tx, new SqlResultCallback() {
@Override
public void onSuccess(SqlTransaction tx, SqlResultSet results) {
rowHandler.onSuccess(tx, results);
promise.resolve(null);
}
});
return promise;
}
}
}