Package org.locationtech.geogig.web.api

Source Code of org.locationtech.geogig.web.api.ResponseWriter

/* 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;
        }
    }
}
TOP

Related Classes of org.locationtech.geogig.web.api.ResponseWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.