package org.activityinfo.ui.client.page.config.design.importer;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gwt.core.client.GWT;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.activityinfo.core.shared.importing.source.SourceColumn;
import org.activityinfo.core.shared.importing.source.SourceRow;
import org.activityinfo.core.shared.importing.source.SourceTable;
import org.activityinfo.legacy.client.Dispatcher;
import org.activityinfo.legacy.shared.command.BatchCommand;
import org.activityinfo.legacy.shared.command.Command;
import org.activityinfo.legacy.shared.command.CreateEntity;
import org.activityinfo.legacy.shared.command.result.BatchResult;
import org.activityinfo.legacy.shared.command.result.CreateResult;
import org.activityinfo.legacy.shared.model.*;
import org.activityinfo.ui.client.page.config.design.importer.wrapper.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SchemaImporter {
interface WarningTemplates extends SafeHtmlTemplates {
@Template("<li>Truncated <code>{0}<strike>{1}</strike></code> (Maximum length: {2} characters)</li>")
SafeHtml truncatedValue(String retained, String truncated, int maxLen);
@Template("<li>There is no LocationType named <code>{0}</code>, using default <code>{1}</code></li>")
SafeHtml invalidLocationType(String name, String defaultValue);
@Template("<li>Using default LocationType <code>{0}</code>, for activity <code>{1}</code></li>")
SafeHtml defaultLocationType(String defaultLocationType, String activityName);
@Template("<li>You didn't provide a column named <code>{0}</code>, " +
"so we'll default to <code>{1}</code>.</li>")
SafeHtml missingColumn(String columnName, String defaultValue);
}
public interface ProgressListener {
void submittingBatch(int batchNumber, int batchCount);
}
private Dispatcher dispatcher;
private UserDatabaseDTO db;
private ProgressListener listener;
private AsyncCallback<Void> callback;
private Map<String, ActivityFormDTO> activityMap = Maps.newHashMap();
private Map<String, Integer> locationTypeMap = Maps.newHashMap();
private List<ActivityFormDTO> newActivities = Lists.newArrayList();
private List<DtoWrapper> newIndicators = Lists.newArrayList();
private List<DtoWrapper> newAttributeGroups = Lists.newArrayList();
private List<DtoWrapper> newAttributes = Lists.newArrayList();
private Set<SafeHtml> warnings = Sets.newHashSet();
public class Column {
private int index;
private String name;
private int maxLength;
private String defaultValue;
public Column(int index, String name, String defaultValue, int maxLength) {
super();
this.index = index;
this.name = name;
this.defaultValue = defaultValue;
this.maxLength = maxLength;
}
public String get(SourceRow row) {
if (index < 0) {
return Strings.emptyToNull(defaultValue);
}
String value = row.getColumnValue(index);
if (value.length() <= maxLength) {
return Strings.emptyToNull(value.trim());
} else {
String retainedValue = value.substring(0, maxLength);
String truncatedValue = value.substring(maxLength);
warnings.add(templates.truncatedValue(retainedValue, truncatedValue, maxLength));
return retainedValue;
}
}
public boolean isMissing() {
return index < 0;
}
public String getName() {
return name;
}
public int getMaxLength() {
return maxLength;
}
}
// columns
private Column activityCategory;
private Column activityName;
private SourceTable source;
private Column formFieldType;
private Column fieldName;
private Column fieldCategory;
private Column fieldDescription;
private Column fieldUnits;
private Column fieldMandatory;
private Column multipleAllowed;
private Column attributeValue;
private Column locationType;
private Column reportingFrequency;
private int batchNumber;
private int batchCount;
private List<String> missingColumns = Lists.newArrayList();
private LocationTypeDTO defaultLocationType;
private boolean fatalError;
private final WarningTemplates templates;
SchemaImporter(Dispatcher dispatcher, UserDatabaseDTO db, WarningTemplates templates) {
this.dispatcher = dispatcher;
this.db = db;
this.templates = templates;
// for (ActivityFormDTO activity : db.getActivities()) {
// activityMap.put(activity.getName() + activity.getCategory(), activity);
// }
for (LocationTypeDTO locationType : db.getCountry().getLocationTypes()) {
locationTypeMap.put(locationType.getName().toLowerCase(), locationType.getId());
}
defaultLocationType = db.getCountry().getLocationTypes().iterator().next();
}
public SchemaImporter(Dispatcher service, UserDatabaseDTO db) {
this(service, db, GWT.<WarningTemplates>create(WarningTemplates.class));
}
public void setProgressListener(ProgressListener listener) {
this.listener = listener;
}
public boolean parseColumns(SourceTable source) {
this.source = source;
this.source.parseAllRows();
findColumns();
return missingColumns.isEmpty();
}
public List<String> getMissingColumns() {
return missingColumns;
}
public boolean processRows() {
processRows(source);
return !fatalError;
}
private void processRows(SourceTable source) {
for (SourceRow row : source.getRows()) {
ActivityFormDTO activity = getActivity(row);
String fieldType = formFieldType.get(row);
if ("Indicator".equals(fieldType)) {
DtoWrapper indicatorWrapper = new DtoWrapper(new IndicatorKey(activity.getName(), fieldName.get(row), fieldCategory.get(row)));
if (!newIndicators.contains(indicatorWrapper)) {
IndicatorDTO indicator = new IndicatorDTO();
indicator.setName(fieldName.get(row));
indicator.setCategory(fieldCategory.get(row));
indicator.setDescription(fieldDescription.get(row));
indicator.setUnits(fieldUnits.get(row));
indicator.set("activityId", activity);
if (isTruthy(fieldMandatory.get(row))) {
indicator.setMandatory(true);
}
indicatorWrapper.setDto(indicator);
newIndicators.add(indicatorWrapper);
}
} else if ("AttributeGroup".equals(fieldType)) {
String name = fieldName.get(row);
AttributeGroupDTO group = activity.getAttributeGroupByName(name);
DtoWrapper attributeGroupWrapper = new DtoWrapper(new AttributeGroupKey(activity.getName(), name));
if (group == null && !newAttributeGroups.contains(attributeGroupWrapper)) {
group = new AttributeGroupDTO();
group.setId(-1);
group.setName(name);
group.set("activityId", activity);
if (isTruthy(multipleAllowed.get(row))) {
group.setMultipleAllowed(true);
}
if (isTruthy(fieldMandatory.get(row))) {
group.setMandatory(true);
}
activity.getAttributeGroups().add(group);
attributeGroupWrapper.setDto(group);
newAttributeGroups.add(attributeGroupWrapper);
}
String attribName = attributeValue.get(row);
AttributeDTO attrib = findAttrib(group, attribName);
DtoWrapper attributeWrapper = new DtoWrapper(new AttributeKey((AttributeGroupKey) attributeGroupWrapper.getKey(), attribName));
if (attrib == null && !newAttributes.contains(attributeWrapper)) {
attrib = new AttributeDTO();
attrib.setId(-1);
attrib.setName(attribName);
attrib.set("attributeGroupId", group);
attributeWrapper.setDto(attrib);
newAttributes.add(attributeWrapper);
}
}
}
}
private AttributeDTO findAttrib(AttributeGroupDTO group, String attribName) {
for (AttributeDTO attrib : group.getAttributes()) {
if (attrib.getName().equals(attribName)) {
return attrib;
}
}
return null;
}
public Set<SafeHtml> getWarnings() {
return warnings;
}
private boolean isTruthy(String columnValue) {
if (columnValue == null) {
return false;
}
String loweredValue = columnValue.toLowerCase().trim();
return loweredValue.equals("1") ||
loweredValue.startsWith("t") || // true
loweredValue.startsWith("y"); // yes
}
private ActivityFormDTO getActivity(SourceRow row) {
String name = activityName.get(row);
String category = activityCategory.get(row);
ActivityFormDTO activity = activityMap.get(name + category);
if (activity == null) {
activity = new ActivityFormDTO();
activity.set("databaseId", db.getId());
activity.setName(name);
activity.setCategory(category);
activity.setLocationTypeId(findLocationType(activity, row));
String frequency = Strings.nullToEmpty(reportingFrequency.get(row));
if (frequency.toLowerCase().contains("month")) {
activity.setReportingFrequency(ActivityFormDTO.REPORT_MONTHLY);
}
activityMap.put(name + category, activity);
newActivities.add(activity);
}
return activity;
}
private int findLocationType(ActivityFormDTO activity, SourceRow row) {
String name = locationType.get(row);
if (Strings.isNullOrEmpty(name)) {
warnings.add(templates.defaultLocationType(defaultLocationType.getName(), activity.getName()));
return defaultLocationType.getId();
} else {
Integer typeId = locationTypeMap.get(name.toLowerCase());
if (typeId == null) {
warnings.add(templates.invalidLocationType(name, defaultLocationType.getName()));
return defaultLocationType.getId();
}
return typeId;
}
}
private int findColumnIndex(String name) {
for (SourceColumn col : source.getColumns()) {
if (col.getHeader().equalsIgnoreCase(name)) {
return col.getIndex();
}
}
return -1;
}
private Column findColumn(String name) {
return findColumn(name, null, Integer.MAX_VALUE);
}
private Column findColumn(String name, String defaultValue) {
return findColumn(name, defaultValue, Integer.MAX_VALUE);
}
private Column findColumn(String name, int maxLength) {
return findColumn(name, null, maxLength);
}
private Column findColumn(String name, String defaultValue, int maxLength) {
int col = findColumnIndex(name);
if (col == -1) {
if (defaultValue == null) {
missingColumns.add(name);
} else if(!Strings.isNullOrEmpty(defaultValue)) {
warnings.add(templates.missingColumn(name, defaultValue));
}
}
return new Column(col, col == -1 ? name : source.getColumnHeader(col), defaultValue, maxLength);
}
private void findColumns() {
missingColumns.clear();
activityCategory = findColumn("ActivityCategory", "", 255);
activityName = findColumn("ActivityName", 45);
locationType = findColumn("LocationType", defaultLocationType.getName());
formFieldType = findColumn("FormFieldType", "quantity");
fieldName = findColumn("Name");
fieldCategory = findColumn("Category", "", 50);
fieldDescription = findColumn("Description", "");
fieldUnits = findColumn("Units", 15);
fieldMandatory = findColumn("Mandatory", "false");
multipleAllowed = findColumn("multipleAllowed", "false");
attributeValue = findColumn("AttributeValue", 50);
reportingFrequency = findColumn("ReportingFrequency", "once");
}
public void persist(AsyncCallback<Void> callback) {
this.callback = callback;
List<List<? extends EntityDTO>> batches = Lists.newArrayList();
batches.add(newActivities);
batches.add(getNewIndicators());
batches.add(getNewAttributeGroups());
batches.add(getNewAttributes());
batchCount = batches.size();
batchNumber = 1;
persistBatch(batches.iterator());
}
private void persistBatch(final Iterator<List<? extends EntityDTO>> batchIterator) {
BatchCommand batchCommand = new BatchCommand();
final List<? extends EntityDTO> batch = batchIterator.next();
for (EntityDTO entity : batch) {
batchCommand.add(create(entity));
}
listener.submittingBatch(batchNumber++, batchCount);
dispatcher.execute(batchCommand, new AsyncCallback<BatchResult>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
@Override
public void onSuccess(BatchResult result) {
for (int i = 0; i != result.getResults().size(); ++i) {
CreateResult createResult = result.getResult(i);
batch.get(i).set("id", createResult.getNewId());
}
if (batchIterator.hasNext()) {
persistBatch(batchIterator);
} else {
callback.onSuccess(null);
}
}
});
}
private Command<CreateResult> create(EntityDTO dto) {
Map<String, Object> map = Maps.newHashMap();
for (String propertyName : dto.getPropertyNames()) {
Object value = dto.get(propertyName);
if (value instanceof EntityDTO) {
map.put(propertyName, ((EntityDTO) value).getId());
} else {
map.put(propertyName, value);
}
}
return new CreateEntity(dto.getEntityName(), map);
}
public void clearWarnings() {
warnings.clear();
fatalError = false;
}
public List<ActivityFormDTO> getNewActivities() {
return newActivities;
}
public List<EntityDTO> getNewIndicators() {
return Wrappers.asDto(newIndicators);
}
public List<EntityDTO> getNewAttributeGroups() {
return Wrappers.asDto(newAttributeGroups);
}
public List<EntityDTO> getNewAttributes() {
return Wrappers.asDto(newAttributes);
}
}