/* 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:
* Gabriel Roldan (Boundless) - initial implementation
*/
package org.locationtech.geogig.web.api;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.codehaus.jettison.AbstractXMLStreamWriter;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.referencing.CRS;
import org.locationtech.geogig.api.Bucket;
import org.locationtech.geogig.api.Context;
import org.locationtech.geogig.api.FeatureBuilder;
import org.locationtech.geogig.api.FeatureInfo;
import org.locationtech.geogig.api.GeogigSimpleFeature;
import org.locationtech.geogig.api.Node;
import org.locationtech.geogig.api.NodeRef;
import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.Remote;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevFeature;
import org.locationtech.geogig.api.RevFeatureBuilder;
import org.locationtech.geogig.api.RevFeatureType;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevPerson;
import org.locationtech.geogig.api.RevTag;
import org.locationtech.geogig.api.RevTree;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.DiffIndex;
import org.locationtech.geogig.api.plumbing.DiffWorkTree;
import org.locationtech.geogig.api.plumbing.FindTreeChild;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.diff.AttributeDiff;
import org.locationtech.geogig.api.plumbing.diff.AttributeDiff.TYPE;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry.ChangeType;
import org.locationtech.geogig.api.plumbing.merge.Conflict;
import org.locationtech.geogig.api.plumbing.merge.MergeScenarioReport;
import org.locationtech.geogig.api.porcelain.BlameReport;
import org.locationtech.geogig.api.porcelain.MergeOp.MergeReport;
import org.locationtech.geogig.api.porcelain.PullResult;
import org.locationtech.geogig.api.porcelain.TransferSummary;
import org.locationtech.geogig.api.porcelain.TransferSummary.ChangedRef;
import org.locationtech.geogig.api.porcelain.ValueAndCommit;
import org.locationtech.geogig.storage.FieldType;
import org.locationtech.geogig.storage.text.CrsTextSerializer;
import org.locationtech.geogig.storage.text.TextValueSerializer;
import org.locationtech.geogig.web.api.commands.BranchWebOp;
import org.locationtech.geogig.web.api.commands.Commit;
import org.locationtech.geogig.web.api.commands.Log.CommitWithChangeCounts;
import org.locationtech.geogig.web.api.commands.LsTree;
import org.locationtech.geogig.web.api.commands.RefParseWeb;
import org.locationtech.geogig.web.api.commands.RemoteWebOp;
import org.locationtech.geogig.web.api.commands.StatisticsWebOp;
import org.locationtech.geogig.web.api.commands.TagWebOp;
import org.locationtech.geogig.web.api.commands.UpdateRefWeb;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.feature.type.PropertyType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* Provides a wrapper for writing common GeoGig objects to a provided {@link XMLStreamWriter}.
*/
public class ResponseWriter {
protected final XMLStreamWriter out;
/**
* Constructs a new {code ResponseWriter} with the given {@link XMLStreamWriter}.
*
* @param out the output stream to write to
*/
public ResponseWriter(XMLStreamWriter out) {
this.out = out;
if (out instanceof AbstractXMLStreamWriter) {
configureJSONOutput((AbstractXMLStreamWriter) out);
}
}
private void configureJSONOutput(AbstractXMLStreamWriter out) {
}
/**
* Ends the document stream.
*
* @throws XMLStreamException
*/
public void finish() throws XMLStreamException {
out.writeEndElement(); // results
out.writeEndDocument();
}
/**
* Begins the document stream.
*
* @throws XMLStreamException
*/
public void start() throws XMLStreamException {
start(true);
}
/**
* Begins the document stream with the provided success flag.
*
* @param success whether or not the operation was successful
* @throws XMLStreamException
*/
public void start(boolean success) throws XMLStreamException {
out.writeStartDocument();
out.writeStartElement("response");
writeElement("success", Boolean.toString(success));
}
/**
* Writes the given header elements to the stream. The array should be organized into key/value
* pairs. For example {@code [key, value, key, value]}.
*
* @param els the elements to write
* @throws XMLStreamException
*/
public void writeHeaderElements(String... els) throws XMLStreamException {
out.writeStartElement("header");
for (int i = 0; i < els.length; i += 2) {
writeElement(els[i], els[i + 1]);
}
out.writeEndElement();
}
/**
* Writes the given error elements to the stream. The array should be organized into key/value
* pairs. For example {@code [key, value, key, value]}.
*
* @param errors the errors to write
* @throws XMLStreamException
*/
public void writeErrors(String... errors) throws XMLStreamException {
out.writeStartElement("errors");
for (int i = 0; i < errors.length; i += 2) {
writeElement(errors[i], errors[i + 1]);
}
out.writeEndElement();
}
/**
* @return the {@link XMLStreamWriter} for this instance
*/
public XMLStreamWriter getWriter() {
return out;
}
/**
* Writes the given element to the stream.
*
* @param element the element name
* @param content the element content
* @throws XMLStreamException
*/
public void writeElement(String element, @Nullable String content) throws XMLStreamException {
out.writeStartElement(element);
if (content != null) {
out.writeCharacters(content);
}
out.writeEndElement();
}
/**
* Writes staged changes to the stream.
*
* @param setFilter the configured {@link DiffIndex} command
* @param start the change number to start writing from
* @param length the number of changes to write
* @throws XMLStreamException
*/
public void writeStaged(DiffIndex setFilter, int start, int length) throws XMLStreamException {
writeDiffEntries("staged", start, length, setFilter.call());
}
/**
* Writes unstaged changes to the stream.
*
* @param setFilter the configured {@link DiffWorkTree} command
* @param start the change number to start writing from
* @param length the number of changes to write
* @throws XMLStreamException
*/
public void writeUnstaged(DiffWorkTree setFilter, int start, int length)
throws XMLStreamException {
writeDiffEntries("unstaged", start, length, setFilter.call());
}
public void writeUnmerged(List<Conflict> conflicts, int start, int length)
throws XMLStreamException {
Iterator<Conflict> entries = conflicts.iterator();
Iterators.advance(entries, start);
if (length < 0) {
length = Integer.MAX_VALUE;
}
for (int i = 0; i < length && entries.hasNext(); i++) {
Conflict entry = entries.next();
out.writeStartElement("unmerged");
writeElement("changeType", "CONFLICT");
writeElement("path", entry.getPath());
writeElement("ours", entry.getOurs().toString());
writeElement("theirs", entry.getTheirs().toString());
writeElement("ancestor", entry.getAncestor().toString());
out.writeEndElement();
}
}
/**
* Writes a set of {@link DiffEntry}s to the stream.
*
* @param name the element name
* @param start the change number to start writing from
* @param length the number of changes to write
* @param entries an iterator for the DiffEntries to write
* @throws XMLStreamException
*/
public void writeDiffEntries(String name, int start, int length, Iterator<DiffEntry> entries)
throws XMLStreamException {
Iterators.advance(entries, start);
if (length < 0) {
length = Integer.MAX_VALUE;
}
int counter = 0;
while (entries.hasNext() && counter < length) {
DiffEntry entry = entries.next();
out.writeStartElement(name);
writeElement("changeType", entry.changeType().toString());
NodeRef oldObject = entry.getOldObject();
NodeRef newObject = entry.getNewObject();
if (oldObject == null) {
writeElement("newPath", newObject.path());
writeElement("newObjectId", newObject.objectId().toString());
writeElement("path", "");
writeElement("oldObjectId", ObjectId.NULL.toString());
} else if (newObject == null) {
writeElement("newPath", "");
writeElement("newObjectId", ObjectId.NULL.toString());
writeElement("path", oldObject.path());
writeElement("oldObjectId", oldObject.objectId().toString());
} else {
writeElement("newPath", newObject.path());
writeElement("newObjectId", newObject.objectId().toString());
writeElement("path", oldObject.path());
writeElement("oldObjectId", oldObject.objectId().toString());
}
out.writeEndElement();
counter++;
}
if (entries.hasNext()) {
writeElement("nextPage", "true");
}
}
public void writeCommit(RevCommit commit, String tag, @Nullable Integer adds,
@Nullable Integer modifies, @Nullable Integer removes) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("id", commit.getId().toString());
writeElement("tree", commit.getTreeId().toString());
ImmutableList<ObjectId> parentIds = commit.getParentIds();
out.writeStartElement("parents");
for (ObjectId parentId : parentIds) {
writeElement("id", parentId.toString());
}
out.writeEndElement();
writePerson("author", commit.getAuthor());
writePerson("committer", commit.getCommitter());
if (adds != null) {
writeElement("adds", adds.toString());
}
if (modifies != null) {
writeElement("modifies", modifies.toString());
}
if (removes != null) {
writeElement("removes", removes.toString());
}
out.writeStartElement("message");
if (commit.getMessage() != null) {
out.writeCData(commit.getMessage());
}
out.writeEndElement();
out.writeEndElement();
}
private void writeNode(Node node, String tag) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("name", node.getName());
writeElement("type", node.getType().name());
writeElement("objectid", node.getObjectId().toString());
writeElement("metadataid", node.getMetadataId().or(ObjectId.NULL).toString());
out.writeEndElement();
}
public void writeTree(RevTree tree, String tag) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("id", tree.getId().toString());
writeElement("size", Long.toString(tree.size()));
writeElement("numtrees", Integer.toString(tree.numTrees()));
if (tree.trees().isPresent()) {
ImmutableList<Node> trees = tree.trees().get();
for (Node ref : trees) {
writeNode(ref, "tree");
}
}
if (tree.features().isPresent()) {
ImmutableList<Node> features = tree.features().get();
for (Node ref : features) {
writeNode(ref, "feature");
}
} else if (tree.buckets().isPresent()) {
Map<Integer, Bucket> buckets = tree.buckets().get();
for (Entry<Integer, Bucket> entry : buckets.entrySet()) {
Integer bucketIndex = entry.getKey();
Bucket bucket = entry.getValue();
out.writeStartElement("bucket");
writeElement("bucketindex", bucketIndex.toString());
writeElement("bucketid", bucket.id().toString());
Envelope env = new Envelope();
env.setToNull();
bucket.expand(env);
out.writeStartElement("bbox");
writeElement("minx", Double.toString(env.getMinX()));
writeElement("maxx", Double.toString(env.getMaxX()));
writeElement("miny", Double.toString(env.getMinY()));
writeElement("maxy", Double.toString(env.getMaxY()));
out.writeEndElement();
out.writeEndElement();
}
}
out.writeEndElement();
}
public void writeFeature(RevFeature feature, String tag) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("id", feature.getId().toString());
ImmutableList<Optional<Object>> values = feature.getValues();
for (Optional<Object> opt : values) {
final FieldType type = FieldType.forValue(opt);
String valueString = TextValueSerializer.asString(opt);
out.writeStartElement("attribute");
writeElement("type", type.toString());
writeElement("value", valueString);
out.writeEndElement();
}
out.writeEndElement();
}
public void writeFeatureType(RevFeatureType featureType, String tag) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("id", featureType.getId().toString());
writeElement("name", featureType.getName().toString());
ImmutableList<PropertyDescriptor> descriptors = featureType.sortedDescriptors();
for (PropertyDescriptor descriptor : descriptors) {
out.writeStartElement("attribute");
writeElement("name", descriptor.getName().toString());
writeElement("type", FieldType.forBinding(descriptor.getType().getBinding()).name());
writeElement("minoccurs", Integer.toString(descriptor.getMinOccurs()));
writeElement("maxoccurs", Integer.toString(descriptor.getMaxOccurs()));
writeElement("nillable", Boolean.toString(descriptor.isNillable()));
PropertyType attrType = descriptor.getType();
if (attrType instanceof GeometryType) {
GeometryType gt = (GeometryType) attrType;
CoordinateReferenceSystem crs = gt.getCoordinateReferenceSystem();
String crsText = CrsTextSerializer.serialize(crs);
writeElement("crs", crsText);
}
out.writeEndElement();
}
out.writeEndElement();
}
public void writeTag(RevTag revTag, String tag) throws XMLStreamException {
out.writeStartElement(tag);
writeElement("id", revTag.getId().toString());
writeElement("commitid", revTag.getCommitId().toString());
writeElement("name", revTag.getName());
writeElement("message", revTag.getMessage());
writePerson("tagger", revTag.getTagger());
out.writeEndElement();
}
/**
* Writes a set of {@link RevCommit}s to the stream.
*
* @param entries an iterator for the RevCommits to write
* @param elementsPerPage the number of commits per page
* @param returnRange only return the range if true
* @throws XMLStreamException
*/
public void writeCommits(Iterator<RevCommit> entries, int elementsPerPage, boolean returnRange)
throws XMLStreamException {
int counter = 0;
RevCommit lastCommit = null;
if (returnRange) {
if (entries.hasNext()) {
lastCommit = entries.next();
writeCommit(lastCommit, "untilCommit", null, null, null);
counter++;
}
}
while (entries.hasNext() && (returnRange || counter < elementsPerPage)) {
lastCommit = entries.next();
if (!returnRange) {
writeCommit(lastCommit, "commit", null, null, null);
}
counter++;
}
if (returnRange) {
if (lastCommit != null) {
writeCommit(lastCommit, "sinceCommit", null, null, null);
}
writeElement("numCommits", Integer.toString(counter));
}
if (entries.hasNext()) {
writeElement("nextPage", "true");
}
}
public void writeCommitsWithChangeCounts(Iterator<CommitWithChangeCounts> entries,
int elementsPerPage) throws XMLStreamException {
int counter = 0;
while (entries.hasNext() && counter < elementsPerPage) {
CommitWithChangeCounts entry = entries.next();
writeCommit(entry.getCommit(), "commit", entry.getAdds(), entry.getModifies(),
entry.getRemoves());
counter++;
}
if (entries.hasNext()) {
writeElement("nextPage", "true");
}
}
/**
* Writes a {@link RevPerson} to the stream.
*
* @param enclosingElement the element name
* @param p the RevPerson to writes
* @throws XMLStreamException
*/
public void writePerson(String enclosingElement, RevPerson p) throws XMLStreamException {
out.writeStartElement(enclosingElement);
writeElement("name", p.getName().orNull());
writeElement("email", p.getEmail().orNull());
writeElement("timestamp", Long.toString(p.getTimestamp()));
writeElement("timeZoneOffset", Long.toString(p.getTimeZoneOffset()));
out.writeEndElement();
}
/**
* Writes the response for the {@link Commit} command to the stream.
*
* @param commit the commit
* @param diff the changes returned from the command
* @throws XMLStreamException
*/
public void writeCommitResponse(RevCommit commit, Iterator<DiffEntry> diff)
throws XMLStreamException {
int adds = 0, deletes = 0, changes = 0;
DiffEntry diffEntry;
while (diff.hasNext()) {
diffEntry = diff.next();
switch (diffEntry.changeType()) {
case ADDED:
++adds;
break;
case REMOVED:
++deletes;
break;
case MODIFIED:
++changes;
break;
}
}
writeElement("commitId", commit.getId().toString());
writeElement("added", Integer.toString(adds));
writeElement("changed", Integer.toString(changes));
writeElement("deleted", Integer.toString(deletes));
}
/**
* Writes the response for the {@link LsTree} command to the stream.
*
* @param iter the iterator of {@link NodeRefs}
* @param verbose if true, more detailed information about each node will be provided
* @throws XMLStreamException
*/
public void writeLsTreeResponse(Iterator<NodeRef> iter, boolean verbose)
throws XMLStreamException {
while (iter.hasNext()) {
NodeRef node = iter.next();
out.writeStartElement("node");
writeElement("path", node.path());
if (verbose) {
writeElement("metadataId", node.getMetadataId().toString());
writeElement("type", node.getType().toString().toLowerCase());
writeElement("objectId", node.objectId().toString());
}
out.writeEndElement();
}
}
/**
* Writes the response for the {@link UpdateRefWeb} command to the stream.
*
* @param ref the ref returned from the command
* @throws XMLStreamException
*/
public void writeUpdateRefResponse(Ref ref) throws XMLStreamException {
out.writeStartElement("ChangedRef");
writeElement("name", ref.getName());
writeElement("objectId", ref.getObjectId().toString());
if (ref instanceof SymRef) {
writeElement("target", ((SymRef) ref).getTarget());
}
out.writeEndElement();
}
/**
* Writes the response for the {@link RefParseWeb} command to the stream.
*
* @param ref the ref returned from the command
* @throws XMLStreamException
*/
public void writeRefParseResponse(Ref ref) throws XMLStreamException {
out.writeStartElement("Ref");
writeElement("name", ref.getName());
writeElement("objectId", ref.getObjectId().toString());
if (ref instanceof SymRef) {
writeElement("target", ((SymRef) ref).getTarget());
}
out.writeEndElement();
}
/**
* Writes an empty ref response for when a {@link Ref} was not found.
*
* @throws XMLStreamException
*/
public void writeEmptyRefResponse() throws XMLStreamException {
out.writeStartElement("RefNotFound");
out.writeEndElement();
}
/**
* Writes the response for the {@link BranchWebOp} command to the stream.
*
* @param localBranches the local branches of the repository
* @param remoteBranches the remote branches of the repository
* @throws XMLStreamException
*/
public void writeBranchListResponse(List<Ref> localBranches, List<Ref> remoteBranches)
throws XMLStreamException {
out.writeStartElement("Local");
for (Ref branch : localBranches) {
out.writeStartElement("Branch");
writeElement("name", branch.localName());
out.writeEndElement();
}
out.writeEndElement();
out.writeStartElement("Remote");
for (Ref branch : remoteBranches) {
if (!(branch instanceof SymRef)) {
out.writeStartElement("Branch");
writeElement("remoteName", branch.namespace().replace(Ref.REMOTES_PREFIX + "/", ""));
writeElement("name", branch.localName());
out.writeEndElement();
}
}
out.writeEndElement();
}
/**
* Writes the response for the {@link RemoteWebOp} command to the stream.
*
* @param remotes the list of the {@link Remote}s of this repository
* @throws XMLStreamException
*/
public void writeRemoteListResponse(List<Remote> remotes, boolean verbose)
throws XMLStreamException {
for (Remote remote : remotes) {
out.writeStartElement("Remote");
writeElement("name", remote.getName());
if (verbose) {
writeElement("url", remote.getFetchURL());
if (remote.getUserName() != null) {
writeElement("username", remote.getUserName());
}
}
out.writeEndElement();
}
}
/**
* Writes the response for the {@link RemoteWebOp} command to the stream.
*
* @param success whether or not the ping was successful
* @throws XMLStreamException
*/
public void writeRemotePingResponse(boolean success) throws XMLStreamException {
out.writeStartElement("ping");
writeElement("success", Boolean.toString(success));
out.writeEndElement();
}
/**
* Writes the response for the {@link TagWebOp} command to the stream.
*
* @param tags the list of {@link RevTag}s of this repository
* @throws XMLStreamException
*/
public void writeTagListResponse(List<RevTag> tags) throws XMLStreamException {
for (RevTag tag : tags) {
out.writeStartElement("Tag");
writeElement("name", tag.getName());
out.writeEndElement();
}
}
public void writeRebuildGraphResponse(ImmutableList<ObjectId> updatedObjects, boolean quiet)
throws XMLStreamException {
out.writeStartElement("RebuildGraph");
if (updatedObjects.size() > 0) {
writeElement("updatedGraphElements", Integer.toString(updatedObjects.size()));
if (!quiet) {
for (ObjectId object : updatedObjects) {
out.writeStartElement("UpdatedObject");
writeElement("ref", object.toString());
out.writeEndElement();
}
}
} else {
writeElement("response",
"No missing or incomplete graph elements (commits) were found.");
}
out.writeEndElement();
}
public void writeFetchResponse(TransferSummary result) throws XMLStreamException {
out.writeStartElement("Fetch");
if (result.getChangedRefs().entrySet().size() > 0) {
for (Entry<String, Collection<ChangedRef>> entry : result.getChangedRefs().entrySet()) {
out.writeStartElement("Remote");
writeElement("remoteName", entry.getKey());
for (ChangedRef ref : entry.getValue()) {
out.writeStartElement("Branch");
writeElement("changeType", ref.getType().toString());
if (ref.getOldRef() != null) {
writeElement("name", ref.getOldRef().localName());
writeElement("oldValue", ref.getOldRef().getObjectId().toString());
}
if (ref.getNewRef() != null) {
if (ref.getOldRef() == null) {
writeElement("name", ref.getNewRef().localName());
}
writeElement("newValue", ref.getNewRef().getObjectId().toString());
}
out.writeEndElement();
}
out.writeEndElement();
}
}
out.writeEndElement();
}
public void writePullResponse(PullResult result, Iterator<DiffEntry> iter, Context geogig)
throws XMLStreamException {
out.writeStartElement("Pull");
writeFetchResponse(result.getFetchResult());
if (iter != null) {
writeElement("Remote", result.getRemoteName());
writeElement("Ref", result.getNewRef().localName());
int added = 0;
int removed = 0;
int modified = 0;
while (iter.hasNext()) {
DiffEntry entry = iter.next();
if (entry.changeType() == ChangeType.ADDED) {
added++;
} else if (entry.changeType() == ChangeType.MODIFIED) {
modified++;
} else if (entry.changeType() == ChangeType.REMOVED) {
removed++;
}
}
writeElement("Added", Integer.toString(added));
writeElement("Modified", Integer.toString(modified));
writeElement("Removed", Integer.toString(removed));
}
if (result.getMergeReport().isPresent()
&& result.getMergeReport().get().getReport().isPresent()) {
MergeReport report = result.getMergeReport().get();
writeMergeResponse(Optional.fromNullable(report.getMergeCommit()), report.getReport()
.get(), geogig, report.getOurs(), report.getPairs().get(0).getTheirs(), report
.getPairs().get(0).getAncestor());
}
out.writeEndElement();
}
/**
* Writes a set of feature diffs to the stream.
*
* @param diffs a map of {@link PropertyDescriptor} to {@link AttributeDiffs} that specify the
* difference between two features
* @throws XMLStreamException
*/
public void writeFeatureDiffResponse(Map<PropertyDescriptor, AttributeDiff> diffs)
throws XMLStreamException {
Set<Entry<PropertyDescriptor, AttributeDiff>> entries = diffs.entrySet();
Iterator<Entry<PropertyDescriptor, AttributeDiff>> iter = entries.iterator();
while (iter.hasNext()) {
Entry<PropertyDescriptor, AttributeDiff> entry = iter.next();
out.writeStartElement("diff");
PropertyType attrType = entry.getKey().getType();
if (attrType instanceof GeometryType) {
writeElement("geometry", "true");
GeometryType gt = (GeometryType) attrType;
CoordinateReferenceSystem crs = gt.getCoordinateReferenceSystem();
if (crs != null) {
String crsCode = null;
try {
crsCode = CRS.lookupIdentifier(Citations.EPSG, crs, false);
} catch (FactoryException e) {
crsCode = null;
}
if (crsCode != null) {
writeElement("crs", "EPSG:" + crsCode);
}
}
}
writeElement("attributename", entry.getKey().getName().toString());
writeElement("changetype", entry.getValue().getType().toString());
if (entry.getValue().getOldValue() != null
&& entry.getValue().getOldValue().isPresent()) {
writeElement("oldvalue", entry.getValue().getOldValue().get().toString());
}
if (entry.getValue().getNewValue() != null
&& entry.getValue().getNewValue().isPresent()
&& !entry.getValue().getType().equals(TYPE.NO_CHANGE)) {
writeElement("newvalue", entry.getValue().getNewValue().get().toString());
}
out.writeEndElement();
}
}
/**
* Writes the response for a set of diffs while also supplying the geometry.
*
* @param geogig - a CommandLocator to call commands from
* @param diff - a DiffEntry iterator to build the response from
* @throws XMLStreamException
*/
public void writeGeometryChanges(final Context geogig, Iterator<DiffEntry> diff, int page,
int elementsPerPage) throws XMLStreamException {
Iterators.advance(diff, page * elementsPerPage);
int counter = 0;
Iterator<GeometryChange> changeIterator = Iterators.transform(diff,
new Function<DiffEntry, GeometryChange>() {
@Override
public GeometryChange apply(DiffEntry input) {
Optional<RevObject> feature = Optional.absent();
Optional<RevObject> type = Optional.absent();
String path = null;
String crsCode = null;
GeometryChange change = null;
if (input.changeType() == ChangeType.ADDED
|| input.changeType() == ChangeType.MODIFIED) {
feature = geogig.command(RevObjectParse.class)
.setObjectId(input.newObjectId()).call();
type = geogig.command(RevObjectParse.class)
.setObjectId(input.getNewObject().getMetadataId()).call();
path = input.getNewObject().path();
} else if (input.changeType() == ChangeType.REMOVED) {
feature = geogig.command(RevObjectParse.class)
.setObjectId(input.oldObjectId()).call();
type = geogig.command(RevObjectParse.class)
.setObjectId(input.getOldObject().getMetadataId()).call();
path = input.getOldObject().path();
}
if (feature.isPresent() && feature.get() instanceof RevFeature
&& type.isPresent() && type.get() instanceof RevFeatureType) {
RevFeatureType featureType = (RevFeatureType) type.get();
Collection<PropertyDescriptor> attribs = featureType.type()
.getDescriptors();
for (PropertyDescriptor attrib : attribs) {
PropertyType attrType = attrib.getType();
if (attrType instanceof GeometryType) {
GeometryType gt = (GeometryType) attrType;
CoordinateReferenceSystem crs = gt
.getCoordinateReferenceSystem();
if (crs != null) {
try {
crsCode = CRS.lookupIdentifier(Citations.EPSG, crs,
false);
} catch (FactoryException e) {
crsCode = null;
}
if (crsCode != null) {
crsCode = "EPSG:" + crsCode;
}
}
break;
}
}
RevFeature revFeature = (RevFeature) feature.get();
FeatureBuilder builder = new FeatureBuilder(featureType);
GeogigSimpleFeature simpleFeature = (GeogigSimpleFeature) builder
.build(revFeature.getId().toString(), revFeature);
change = new GeometryChange(simpleFeature, input.changeType(), path,
crsCode);
}
return change;
}
});
while (changeIterator.hasNext() && (elementsPerPage == 0 || counter < elementsPerPage)) {
GeometryChange next = changeIterator.next();
if (next != null) {
GeogigSimpleFeature feature = next.getFeature();
ChangeType change = next.getChangeType();
out.writeStartElement("Feature");
writeElement("change", change.toString());
writeElement("id", next.getPath());
List<Object> attributes = feature.getAttributes();
for (Object attribute : attributes) {
if (attribute instanceof Geometry) {
writeElement("geometry", ((Geometry) attribute).toText());
break;
}
}
if (next.getCRS() != null) {
writeElement("crs", next.getCRS());
}
out.writeEndElement();
counter++;
}
}
if (changeIterator.hasNext()) {
writeElement("nextPage", "true");
}
}
/**
* Writes the response for a set of conflicts while also supplying the geometry.
*
* @param geogig - a CommandLocator to call commands from
* @param conflicts - a Conflict iterator to build the response from
* @throws XMLStreamException
*/
public void writeConflicts(final Context geogig, Iterator<Conflict> conflicts,
final ObjectId ours, final ObjectId theirs) throws XMLStreamException {
Iterator<GeometryConflict> conflictIterator = Iterators.transform(conflicts,
new Function<Conflict, GeometryConflict>() {
@Override
public GeometryConflict apply(Conflict input) {
ObjectId commitId = ours;
if (input.getOurs().equals(ObjectId.NULL)) {
commitId = theirs;
}
Optional<RevObject> object = geogig.command(RevObjectParse.class)
.setObjectId(commitId).call();
RevCommit commit = null;
if (object.isPresent() && object.get() instanceof RevCommit) {
commit = (RevCommit) object.get();
} else {
throw new CommandSpecException("Couldn't resolve id: "
+ commitId.toString() + " to a commit");
}
object = geogig.command(RevObjectParse.class)
.setObjectId(commit.getTreeId()).call();
Optional<NodeRef> node = Optional.absent();
if (object.isPresent()) {
RevTree tree = (RevTree) object.get();
node = geogig.command(FindTreeChild.class).setParent(tree)
.setChildPath(input.getPath()).call();
} else {
throw new CommandSpecException("Couldn't resolve commit's treeId");
}
RevFeatureType type = null;
RevFeature feature = null;
if (node.isPresent()) {
object = geogig.command(RevObjectParse.class)
.setObjectId(node.get().getMetadataId()).call();
if (object.isPresent() && object.get() instanceof RevFeatureType) {
type = (RevFeatureType) object.get();
} else {
throw new CommandSpecException(
"Couldn't resolve newCommit's featureType");
}
object = geogig.command(RevObjectParse.class)
.setObjectId(node.get().objectId()).call();
if (object.isPresent() && object.get() instanceof RevFeature) {
feature = (RevFeature) object.get();
} else {
throw new CommandSpecException(
"Couldn't resolve newCommit's feature");
}
}
GeometryConflict conflict = null;
if (feature != null && type != null) {
String crsCode = null;
Collection<PropertyDescriptor> attribs = type.type().getDescriptors();
for (PropertyDescriptor attrib : attribs) {
PropertyType attrType = attrib.getType();
if (attrType instanceof GeometryType) {
GeometryType gt = (GeometryType) attrType;
CoordinateReferenceSystem crs = gt
.getCoordinateReferenceSystem();
if (crs != null) {
try {
crsCode = CRS.lookupIdentifier(Citations.EPSG, crs,
false);
} catch (FactoryException e) {
crsCode = null;
}
if (crsCode != null) {
crsCode = "EPSG:" + crsCode;
}
}
break;
}
}
FeatureBuilder builder = new FeatureBuilder(type);
GeogigSimpleFeature simpleFeature = (GeogigSimpleFeature) builder
.build(feature.getId().toString(), feature);
Geometry geom = null;
List<Object> attributes = simpleFeature.getAttributes();
for (Object attribute : attributes) {
if (attribute instanceof Geometry) {
geom = (Geometry) attribute;
break;
}
}
conflict = new GeometryConflict(input, geom, crsCode);
}
return conflict;
}
});
while (conflictIterator.hasNext()) {
GeometryConflict next = conflictIterator.next();
if (next != null) {
out.writeStartElement("Feature");
writeElement("change", "CONFLICT");
writeElement("id", next.getConflict().getPath());
writeElement("ourvalue", next.getConflict().getOurs().toString());
writeElement("theirvalue", next.getConflict().getTheirs().toString());
writeElement("geometry", next.getGeometry().toText());
if (next.getCRS() != null) {
writeElement("crs", next.getCRS());
}
out.writeEndElement();
}
}
}
/**
* Writes the response for a set of merged features while also supplying the geometry.
*
* @param geogig - a CommandLocator to call commands from
* @param features - a FeatureInfo iterator to build the response from
* @throws XMLStreamException
*/
public void writeMerged(final Context geogig, Iterator<FeatureInfo> features)
throws XMLStreamException {
Iterator<GeometryChange> changeIterator = Iterators.transform(features,
new Function<FeatureInfo, GeometryChange>() {
@Override
public GeometryChange apply(FeatureInfo input) {
GeometryChange change = null;
RevFeature revFeature = RevFeatureBuilder.build(input.getFeature());
RevFeatureType featureType = input.getFeatureType();
Collection<PropertyDescriptor> attribs = featureType.type()
.getDescriptors();
String crsCode = null;
for (PropertyDescriptor attrib : attribs) {
PropertyType attrType = attrib.getType();
if (attrType instanceof GeometryType) {
GeometryType gt = (GeometryType) attrType;
CoordinateReferenceSystem crs = gt.getCoordinateReferenceSystem();
if (crs != null) {
try {
crsCode = CRS.lookupIdentifier(Citations.EPSG, crs, false);
} catch (FactoryException e) {
crsCode = null;
}
if (crsCode != null) {
crsCode = "EPSG:" + crsCode;
}
}
break;
}
}
FeatureBuilder builder = new FeatureBuilder(featureType);
GeogigSimpleFeature simpleFeature = (GeogigSimpleFeature) builder.build(
revFeature.getId().toString(), revFeature);
change = new GeometryChange(simpleFeature, ChangeType.MODIFIED, input
.getPath(), crsCode);
return change;
}
});
while (changeIterator.hasNext()) {
GeometryChange next = changeIterator.next();
if (next != null) {
GeogigSimpleFeature feature = next.getFeature();
out.writeStartElement("Feature");
writeElement("change", "MERGED");
writeElement("id", next.getPath());
List<Object> attributes = feature.getAttributes();
for (Object attribute : attributes) {
if (attribute instanceof Geometry) {
writeElement("geometry", ((Geometry) attribute).toText());
break;
}
}
if (next.getCRS() != null) {
writeElement("crs", next.getCRS());
}
out.writeEndElement();
}
}
}
/**
* Writes the response for a merge dry-run, contains unconflicted, conflicted and merged
* features.
*
* @param report - the MergeScenarioReport containing all the merge results
* @param transaction - a transaction aware injector to call commands from
* @throws XMLStreamException
*/
public void writeMergeResponse(Optional<RevCommit> mergeCommit, MergeScenarioReport report,
Context transaction, ObjectId ours, ObjectId theirs, ObjectId ancestor)
throws XMLStreamException {
out.writeStartElement("Merge");
writeElement("ours", ours.toString());
writeElement("theirs", theirs.toString());
writeElement("ancestor", ancestor.toString());
if (mergeCommit.isPresent()) {
writeElement("mergedCommit", mergeCommit.get().getId().toString());
}
if (report.getConflicts().size() > 0) {
writeElement("conflicts", Integer.toString(report.getConflicts().size()));
}
writeGeometryChanges(transaction, report.getUnconflicted().iterator(), 0, 0);
writeConflicts(transaction, report.getConflicts().iterator(), ours, theirs);
writeMerged(transaction, report.getMerged().iterator());
out.writeEndElement();
}
/**
* Writes the id of the transaction created or nothing if it was ended successfully.
*
* @param transactionId - the id of the transaction or null if the transaction was closed
* successfully
* @throws XMLStreamException
*/
public void writeTransactionId(UUID transactionId) throws XMLStreamException {
out.writeStartElement("Transaction");
if (transactionId != null) {
writeElement("ID", transactionId.toString());
}
out.writeEndElement();
}
/**
* Writes the response for the blame operation.
*
* @param report - the result of the blame operation
* @throws XMLStreamException
*/
public void writeBlameReport(BlameReport report) throws XMLStreamException {
out.writeStartElement("Blame");
Map<String, ValueAndCommit> changes = report.getChanges();
Iterator<String> iter = changes.keySet().iterator();
while (iter.hasNext()) {
String attrib = iter.next();
ValueAndCommit valueAndCommit = changes.get(attrib);
RevCommit commit = valueAndCommit.commit;
Optional<?> value = valueAndCommit.value;
out.writeStartElement("Attribute");
writeElement("name", attrib);
writeElement("value",
TextValueSerializer.asString(Optional.fromNullable((Object) value.orNull())));
writeCommit(commit, "commit", null, null, null);
out.writeEndElement();
}
out.writeEndElement();
}
public void writeStatistics(List<StatisticsWebOp.FeatureTypeStats> stats,
RevCommit firstCommit, RevCommit lastCommit, int totalCommits, List<RevPerson> authors,
int totalAdded, int totalModified, int totalRemoved) throws XMLStreamException {
out.writeStartElement("Statistics");
int numFeatureTypes = 0;
int totalNumFeatures = 0;
if (!stats.isEmpty()) {
out.writeStartElement("FeatureTypes");
for (StatisticsWebOp.FeatureTypeStats stat : stats) {
numFeatureTypes++;
out.writeStartElement("FeatureType");
writeElement("name", stat.getName());
writeElement("numFeatures", Long.toString(stat.getNumFeatures()));
totalNumFeatures += stat.getNumFeatures();
out.writeEndElement();
}
if (numFeatureTypes > 1) {
writeElement("totalFeatureTypes", Integer.toString(numFeatureTypes));
writeElement("totalFeatures", Integer.toString(totalNumFeatures));
}
out.writeEndElement();
}
if (lastCommit != null) {
writeCommit(lastCommit, "latestCommit", null, null, null);
}
if (firstCommit != null) {
writeCommit(firstCommit, "firstCommit", null, null, null);
}
if (totalCommits > 0) {
writeElement("totalCommits", Integer.toString(totalCommits));
}
if (totalAdded > 0) {
writeElement("totalAdded", Integer.toString(totalAdded));
}
if (totalRemoved > 0) {
writeElement("totalRemoved", Integer.toString(totalRemoved));
}
if (totalModified > 0) {
writeElement("totalModified", Integer.toString(totalModified));
}
{
out.writeStartElement("Authors");
for (RevPerson author : authors) {
if (author.getName().isPresent() || author.getEmail().isPresent()) {
out.writeStartElement("Author");
if (author.getName().isPresent()) {
writeElement("name", author.getName().get());
}
if (author.getEmail().isPresent()) {
writeElement("email", author.getEmail().get());
}
out.writeEndElement();
}
}
writeElement("totalAuthors", Integer.toString(authors.size()));
out.writeEndElement();
}
out.writeEndElement();
}
private class GeometryChange {
private GeogigSimpleFeature feature;
private ChangeType changeType;
private String path;
private String crs;
public GeometryChange(GeogigSimpleFeature feature, ChangeType changeType, String path,
String crs) {
this.feature = feature;
this.changeType = changeType;
this.path = path;
this.crs = crs;
}
public GeogigSimpleFeature getFeature() {
return feature;
}
public ChangeType getChangeType() {
return changeType;
}
public String getPath() {
return path;
}
public String getCRS() {
return crs;
}
}
private class GeometryConflict {
private Conflict conflict;
private Geometry geom;
private String crs;
public GeometryConflict(Conflict conflict, Geometry geom, String crs) {
this.conflict = conflict;
this.geom = geom;
this.crs = crs;
}
public Conflict getConflict() {
return conflict;
}
public Geometry getGeometry() {
return geom;
}
public String getCRS() {
return crs;
}
}
}