/* Copyright (c) 2013-2014 Boundless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Kelsey Ishmael (LMN Solutions) - initial implementation
*/
package org.locationtech.geogig.web.api.commands;
import java.util.HashMap;
import java.util.Map;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.plumbing.FindTreeChild;
import org.locationtech.geogig.api.plumbing.ResolveTreeish;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.diff.AttributeDiff;
import org.locationtech.geogig.api.plumbing.diff.FeatureDiff;
import org.locationtech.geogig.api.plumbing.diff.GenericAttributeDiffImpl;
import org.locationtech.geogig.api.plumbing.diff.GeometryAttributeDiff;
import org.locationtech.geogig.web.api.AbstractWebAPICommand;
import org.locationtech.geogig.web.api.CommandContext;
import org.locationtech.geogig.web.api.CommandResponse;
import org.locationtech.geogig.web.api.CommandSpecException;
import org.locationtech.geogig.web.api.ResponseWriter;
import org.opengis.feature.type.PropertyDescriptor;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.vividsolutions.jts.geom.Geometry;
/**
* This is the interface for the FeatureDiff command. It is used by passing a path to a feature, an
* oldCommitId and a newCommitId. It returns the differences in the attributes of a feature between
* the two supplied commits.
*
* Web interface for {@link FeatureDiff}
*/
public class FeatureDiffWeb extends AbstractWebAPICommand {
private String path;
private String newTreeish;
private String oldTreeish;
private boolean all;
/**
* Mutator of the path variable
*
* @param path - the path to the feature
*/
public void setPath(String path) {
this.path = path;
}
/**
* Mutator for the newTreeish
*
* @param newTreeish - the id of the newer commit
*/
public void setNewTreeish(String newTreeish) {
this.newTreeish = newTreeish;
}
/**
* Mutator for the oldTreeish
*
* @param oldTreeish - the id of the older commit
*/
public void setOldTreeish(String oldTreeish) {
this.oldTreeish = oldTreeish;
}
/**
* Mutator for all attributes bool
*
* @param all - true to show all attributes not just changed ones
*/
public void setAll(boolean all) {
this.all = all;
}
/**
* Helper function to parse the given commit id's feature information
*
* @param id - the id to parse out
* @param geogig - an instance of geogig to run commands with
* @return (Optional)NodeRef - the NodeRef that contains the metadata id and id needed to get
* the feature and featuretype
*
* @throws CommandSpecException - if the treeid couldn't be resolved
*/
private Optional<NodeRef> parseID(ObjectId id, Context geogig) {
Optional<RevObject> object = geogig.command(RevObjectParse.class).setObjectId(id).call();
if (object.isPresent()) {
RevTree tree = (RevTree) object.get();
return geogig.command(FindTreeChild.class).setParent(tree).setChildPath(path).call();
} else {
throw new CommandSpecException("Couldn't resolve treeId");
}
}
/**
* Runs the command and builds the appropriate response
*
* @param context - the context to use for this command
*
* @throws CommandSpecException
*/
@Override
public void run(CommandContext context) {
if (path == null || path.trim().isEmpty()) {
throw new CommandSpecException("No path for feature name specifed");
}
final Context geogig = this.getCommandLocator(context);
ObjectId newId = geogig.command(ResolveTreeish.class).setTreeish(newTreeish).call().get();
ObjectId oldId = geogig.command(ResolveTreeish.class).setTreeish(oldTreeish).call().get();
RevFeature newFeature = null;
RevFeatureType newFeatureType = null;
RevFeature oldFeature = null;
RevFeatureType oldFeatureType = null;
final Map<PropertyDescriptor, AttributeDiff> diffs;
Optional<NodeRef> ref = parseID(newId, geogig);
Optional<RevObject> object;
// need these to determine if the feature was added or removed so I can build the diffs
// myself until the FeatureDiff supports null values
boolean removed = false;
boolean added = false;
if (ref.isPresent()) {
object = geogig.command(RevObjectParse.class).setObjectId(ref.get().getMetadataId())
.call();
if (object.isPresent() && object.get() instanceof RevFeatureType) {
newFeatureType = (RevFeatureType) object.get();
} else {
throw new CommandSpecException("Couldn't resolve newCommit's featureType");
}
object = geogig.command(RevObjectParse.class).setObjectId(ref.get().objectId()).call();
if (object.isPresent() && object.get() instanceof RevFeature) {
newFeature = (RevFeature) object.get();
} else {
throw new CommandSpecException("Couldn't resolve newCommit's feature");
}
} else {
removed = true;
}
if (!oldId.equals(ObjectId.NULL)) {
ref = parseID(oldId, geogig);
if (ref.isPresent()) {
object = geogig.command(RevObjectParse.class)
.setObjectId(ref.get().getMetadataId()).call();
if (object.isPresent() && object.get() instanceof RevFeatureType) {
oldFeatureType = (RevFeatureType) object.get();
} else {
throw new CommandSpecException("Couldn't resolve oldCommit's featureType");
}
object = geogig.command(RevObjectParse.class).setObjectId(ref.get().objectId())
.call();
if (object.isPresent() && object.get() instanceof RevFeature) {
oldFeature = (RevFeature) object.get();
} else {
throw new CommandSpecException("Couldn't resolve oldCommit's feature");
}
} else {
added = true;
}
} else {
added = true;
}
if (removed) {
Map<PropertyDescriptor, AttributeDiff> tempDiffs = new HashMap<PropertyDescriptor, AttributeDiff>();
ImmutableList<PropertyDescriptor> attributes = oldFeatureType.sortedDescriptors();
ImmutableList<Optional<Object>> values = oldFeature.getValues();
for (int index = 0; index < attributes.size(); index++) {
Optional<Object> value = values.get(index);
if (Geometry.class.isAssignableFrom(attributes.get(index).getType().getBinding())) {
Optional<Geometry> temp = Optional.absent();
if (value.isPresent() || all) {
tempDiffs.put(
attributes.get(index),
new GeometryAttributeDiff(Optional.fromNullable((Geometry) value
.orNull()), temp));
}
} else {
if (value.isPresent() || all) {
tempDiffs.put(attributes.get(index), new GenericAttributeDiffImpl(value,
Optional.absent()));
}
}
}
diffs = tempDiffs;
} else if (added) {
Map<PropertyDescriptor, AttributeDiff> tempDiffs = new HashMap<PropertyDescriptor, AttributeDiff>();
ImmutableList<PropertyDescriptor> attributes = newFeatureType.sortedDescriptors();
ImmutableList<Optional<Object>> values = newFeature.getValues();
for (int index = 0; index < attributes.size(); index++) {
Optional<Object> value = values.get(index);
if (Geometry.class.isAssignableFrom(attributes.get(index).getType().getBinding())) {
Optional<Geometry> temp = Optional.absent();
if (value.isPresent() || all) {
tempDiffs.put(attributes.get(index), new GeometryAttributeDiff(temp,
Optional.fromNullable((Geometry) value.orNull())));
}
} else {
if (value.isPresent() || all) {
tempDiffs.put(attributes.get(index),
new GenericAttributeDiffImpl(Optional.absent(), value));
}
}
}
diffs = tempDiffs;
} else {
FeatureDiff diff = new FeatureDiff(path, newFeature, oldFeature, newFeatureType,
oldFeatureType, all);
diffs = diff.getDiffs();
}
context.setResponseContent(new CommandResponse() {
@Override
public void write(ResponseWriter out) throws Exception {
out.start();
out.writeFeatureDiffResponse(diffs);
out.finish();
}
});
}
}