package org.activityinfo.legacy.shared.adapter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.activityinfo.core.shared.application.ApplicationProperties;
import org.activityinfo.core.shared.application.FolderClass;
import org.activityinfo.core.shared.criteria.Criteria;
import org.activityinfo.core.shared.criteria.CriteriaIntersection;
import org.activityinfo.core.shared.criteria.FieldCriteria;
import org.activityinfo.model.form.FormInstance;
import org.activityinfo.legacy.client.Dispatcher;
import org.activityinfo.legacy.shared.command.*;
import org.activityinfo.model.legacy.CuidAdapter;
import org.activityinfo.model.resource.ResourceId;
import org.activityinfo.model.type.ReferenceValue;
import org.activityinfo.promise.ConcatList;
import org.activityinfo.promise.Promise;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static org.activityinfo.model.legacy.CuidAdapter.*;
import static org.activityinfo.promise.BiFunctions.concatMap;
/**
* Given an intersection of Criteria, fetch the corresponding entities
*/
public class QueryExecutor {
private final Dispatcher dispatcher;
private final Criteria criteria;
private CriteriaAnalysis criteriaAnalysis;
public QueryExecutor(Dispatcher dispatcher, Criteria rootCriteria) {
this.dispatcher = dispatcher;
this.criteria = rootCriteria;
this.criteriaAnalysis = CriteriaAnalysis.analyze(rootCriteria);
}
public Promise<List<FormInstance>> execute() {
if (criteriaAnalysis.isEmptySet()) {
return emptySet();
}
if (criteriaAnalysis.isRestrictedToSingleClass()) {
return queryByClassId(criteriaAnalysis.getClassRestriction());
} else if (criteriaAnalysis.isRestrictedByUnionOfClasses()) {
return queryByClassIds();
} else if (criteriaAnalysis.isRestrictedById()) {
List<Promise<List<FormInstance>>> resultSets = Lists.newArrayList();
for (Character domain : criteriaAnalysis.getIds().keySet()) {
resultSets.add(queryByIds(domain, criteriaAnalysis.getIds().get(domain)));
}
return Promise.foldLeft(Collections.<FormInstance>emptyList(), new ConcatList<FormInstance>(), resultSets);
} else if (criteriaAnalysis.isAncestorQuery()) {
ResourceId parentId = criteriaAnalysis.getParentCriteria();
if (parentId.equals(FolderListAdapter.HOME_ID) || parentId.getDomain() == DATABASE_DOMAIN ||
parentId.getDomain() == ACTIVITY_CATEGORY_DOMAIN) {
return folders();
} else if (parentId.equals(FolderListAdapter.GEODB_ID)) {
return countries();
} else if (parentId.getDomain() == CuidAdapter.COUNTRY_DOMAIN) {
return adminLevels(CuidAdapter.getLegacyIdFromCuid(parentId));
} else {
throw new UnsupportedOperationException("parentID " + parentId);
}
} else {
throw new UnsupportedOperationException("queries must have either class criteria or parent criteria");
}
}
private Promise<List<FormInstance>> adminLevels(int countryId) {
GetAdminLevels query = new GetAdminLevels();
query.setCountryId(countryId);
return dispatcher.execute(query).then(new ListResultAdapter<>(new AdminLevelInstanceAdapter()));
}
private Promise<List<FormInstance>> queryByClassIds() {
final Set<ResourceId> classCriteria = criteriaAnalysis.getClassCriteria();
final List<Promise<List<FormInstance>>> resultSets = Lists.newArrayList();
for (ResourceId classId : classCriteria) {
resultSets.add(queryByClassId(classId));
}
return Promise.foldLeft(Collections.<FormInstance>emptyList(), new ConcatList<FormInstance>(), resultSets);
}
private Promise<List<FormInstance>> queryByIds(char domain, Collection<Integer> ids) {
switch (domain) {
case ADMIN_ENTITY_DOMAIN:
GetAdminEntities entityQuery = new GetAdminEntities();
if (!ids.isEmpty()) {
entityQuery.setEntityIds(ids);
}
return dispatcher.execute(entityQuery).then(new ListResultAdapter<>(new AdminEntityInstanceAdapter()));
case LOCATION_DOMAIN:
return dispatcher.execute(new GetLocations(Lists.newArrayList(ids)))
.then(new ListResultAdapter<>(new LocationInstanceAdapter()));
case COUNTRY_DOMAIN:
return countries();
case '_': // system objects
case 'h': // home
case DATABASE_DOMAIN:
case ACTIVITY_CATEGORY_DOMAIN:
case ACTIVITY_DOMAIN:
case LOCATION_TYPE_DOMAIN:
return folders();
}
throw new UnsupportedOperationException("unrecognized domain: " + domain);
}
private Promise<List<FormInstance>> countries() {
return dispatcher.execute(new GetCountries()).then(new ListResultAdapter<>(new CountryInstanceAdapter()));
}
private Promise<List<FormInstance>> queryByClassId(ResourceId formClassId) {
if (formClassId.equals(FolderClass.CLASS_ID)) {
return folders();
} else if (formClassId.equals(ApplicationProperties.COUNTRY_CLASS)) {
return countries();
}
switch (formClassId.getDomain()) {
case ADMIN_LEVEL_DOMAIN:
return dispatcher.execute(adminQuery(formClassId))
.then(new ListResultAdapter<>(new AdminEntityInstanceAdapter()));
case LOCATION_TYPE_DOMAIN:
return dispatcher.execute(composeLocationQuery(formClassId))
.then(new ListResultAdapter<>(new LocationInstanceAdapter()));
case PARTNER_FORM_CLASS_DOMAIN:
return dispatcher.execute(new GetSchema())
.then(new PartnerListExtractor(criteria))
.then(concatMap(new PartnerInstanceAdapter(formClassId)));
case PROJECT_CLASS_DOMAIN:
return dispatcher.execute(new GetSchema())
.then(new ProjectListExtractor(criteria))
.then(concatMap(new ProjectInstanceAdapter(formClassId)));
default:
return Promise.rejected(new UnsupportedOperationException(
"domain not yet implemented: " + formClassId.getDomain()));
}
}
private GetAdminEntities adminQuery(ResourceId formClassId) {
GetAdminEntities query = new GetAdminEntities();
query.setLevelId(CuidAdapter.getLegacyIdFromCuid(formClassId));
Multimap<Character, Integer> ids = criteriaAnalysis.getIds();
if (!ids.get(ADMIN_ENTITY_DOMAIN).isEmpty()) {
query.setEntityIds(ids.get(ADMIN_ENTITY_DOMAIN));
}
if (criteria instanceof CriteriaIntersection) {
for (Criteria element : ((CriteriaIntersection) criteria).getElements()) {
if (element instanceof FieldCriteria) {
FieldCriteria fieldCriteria = (FieldCriteria) element;
if (fieldCriteria.getFieldId().equals(CuidAdapter.field(formClassId, ADMIN_PARENT_FIELD))) {
ReferenceValue id = (ReferenceValue) fieldCriteria.getValue();
query.setParentId(CuidAdapter.getLegacyIdFromCuid(Iterables.getOnlyElement(id.getResourceIds())));
}
}
}
}
return query;
}
private Promise<List<FormInstance>> folders() {
return dispatcher.execute(new GetSchema()).then(new FolderListAdapter(criteria));
}
private GetLocations composeLocationQuery(ResourceId formClassId) {
int locationTypeId = CuidAdapter.getLegacyIdFromCuid(formClassId);
GetLocations searchLocations = new GetLocations();
searchLocations.setLocationTypeId(locationTypeId);
searchLocations.setLocationIds(criteriaAnalysis.getIds(CuidAdapter.LOCATION_DOMAIN));
return searchLocations;
}
private Promise<List<FormInstance>> emptySet() {
return Promise.resolved(Collections.<FormInstance>emptyList());
}
}