From 0e951feb3ad7b673cd90d6aa57b21535bda3d5bf Mon Sep 17 00:00:00 2001 From: Kelsey Ishmael Date: Fri, 2 May 2014 13:40:34 -0400 Subject: [PATCH] Added a Statistics web-api command. --- .../org/geogit/web/api/CommandBuilder.java | 10 + .../org/geogit/web/api/ResponseWriter.java | 68 ++++++- .../web/api/commands/StatisticsWebOp.java | 172 ++++++++++++++++++ 3 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/web/api/src/main/java/org/geogit/web/api/commands/StatisticsWebOp.java diff --git a/src/web/api/src/main/java/org/geogit/web/api/CommandBuilder.java b/src/web/api/src/main/java/org/geogit/web/api/CommandBuilder.java index 0640c5304..efd637b32 100644 --- a/src/web/api/src/main/java/org/geogit/web/api/CommandBuilder.java +++ b/src/web/api/src/main/java/org/geogit/web/api/CommandBuilder.java @@ -29,6 +29,7 @@ import org.geogit.web.api.commands.RemoveWebOp; import org.geogit.web.api.commands.ResolveConflict; import org.geogit.web.api.commands.RevertFeatureWebOp; +import org.geogit.web.api.commands.StatisticsWebOp; import org.geogit.web.api.commands.Status; import org.geogit.web.api.commands.TagWebOp; import org.geogit.web.api.commands.UpdateRefWeb; @@ -103,6 +104,8 @@ public static WebAPICommand build(String commandName, ParameterSet options) command = buildBlame(options); } else if ("version".equalsIgnoreCase(commandName)) { command = buildVersion(options); + } else if ("statistics".equalsIgnoreCase(commandName)) { + command = buildStatistics(options); } else { throw new CommandSpecException("'" + commandName + "' is not a geogit command"); } @@ -494,4 +497,11 @@ static BlameWebOp buildBlame(ParameterSet options) { return command; } + static StatisticsWebOp buildStatistics(ParameterSet options) { + StatisticsWebOp command = new StatisticsWebOp(); + command.setPath(options.getFirstValue("path", null)); + command.setSince(options.getFirstValue("since", null)); + command.setUntil(options.getFirstValue("branch", null)); + return command; + } } diff --git a/src/web/api/src/main/java/org/geogit/web/api/ResponseWriter.java b/src/web/api/src/main/java/org/geogit/web/api/ResponseWriter.java index ffb1d1a80..91538f106 100644 --- a/src/web/api/src/main/java/org/geogit/web/api/ResponseWriter.java +++ b/src/web/api/src/main/java/org/geogit/web/api/ResponseWriter.java @@ -17,10 +17,10 @@ import javax.xml.stream.XMLStreamWriter; import org.codehaus.jettison.AbstractXMLStreamWriter; +import org.geogit.api.Context; import org.geogit.api.FeatureBuilder; import org.geogit.api.FeatureInfo; import org.geogit.api.GeogitSimpleFeature; -import org.geogit.api.Context; import org.geogit.api.NodeRef; import org.geogit.api.ObjectId; import org.geogit.api.Ref; @@ -57,6 +57,7 @@ import org.geogit.web.api.commands.LsTree; import org.geogit.web.api.commands.RefParseWeb; import org.geogit.web.api.commands.RemoteWebOp; +import org.geogit.web.api.commands.StatisticsWebOp; import org.geogit.web.api.commands.TagWebOp; import org.geogit.web.api.commands.UpdateRefWeb; import org.geotools.metadata.iso.citation.Citations; @@ -687,8 +688,8 @@ public void writeFeatureDiffResponse(Map diff * @param diff - a DiffEntry iterator to build the response from * @throws XMLStreamException */ - public void writeGeometryChanges(final Context geogit, Iterator diff, - int page, int elementsPerPage) throws XMLStreamException { + public void writeGeometryChanges(final Context geogit, Iterator diff, int page, + int elementsPerPage) throws XMLStreamException { Iterators.advance(diff, page * elementsPerPage); int counter = 0; @@ -1041,6 +1042,67 @@ public void writeBlameReport(BlameReport report) throws XMLStreamException { out.writeEndElement(); } + public void writeStatistics(List stats, + RevCommit firstCommit, RevCommit lastCommit, int totalCommits, List 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)); + } + if (!authors.isEmpty()) { + out.writeStartElement("Authors"); + + for (RevPerson author : authors) { + 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 GeogitSimpleFeature feature; diff --git a/src/web/api/src/main/java/org/geogit/web/api/commands/StatisticsWebOp.java b/src/web/api/src/main/java/org/geogit/web/api/commands/StatisticsWebOp.java new file mode 100644 index 000000000..1aa45d987 --- /dev/null +++ b/src/web/api/src/main/java/org/geogit/web/api/commands/StatisticsWebOp.java @@ -0,0 +1,172 @@ +/* Copyright (c) 2013 OpenPlans. All rights reserved. + * This code is licensed under the BSD New License, available at the root + * application directory. + */ +package org.geogit.web.api.commands; + +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.geogit.api.Context; +import org.geogit.api.NodeRef; +import org.geogit.api.ObjectId; +import org.geogit.api.RevCommit; +import org.geogit.api.RevPerson; +import org.geogit.api.plumbing.LsTreeOp; +import org.geogit.api.plumbing.ParseTimestamp; +import org.geogit.api.plumbing.RevParse; +import org.geogit.api.plumbing.diff.DiffEntry; +import org.geogit.api.porcelain.DiffOp; +import org.geogit.api.porcelain.LogOp; +import org.geogit.web.api.AbstractWebAPICommand; +import org.geogit.web.api.CommandContext; +import org.geogit.web.api.CommandResponse; +import org.geogit.web.api.ResponseWriter; +import org.geotools.util.Range; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +/** + * List certain statistics of repository. + */ + +public class StatisticsWebOp extends AbstractWebAPICommand { + + String path; + + String since; + + String until; + + public void setPath(String path) { + this.path = path; + } + + public void setSince(String since) { + this.since = since; + } + + public void setUntil(String until) { + this.until = until; + } + + /** + * Runs the command and builds the appropriate response + * + * @param context - the context to use for this command + */ + @Override + public void run(CommandContext context) { + final Context geogit = this.getCommandLocator(context); + final List stats = Lists.newArrayList(); + LogOp logOp = geogit.command(LogOp.class).setFirstParentOnly(true); + final Iterator log; + if (since != null && !since.trim().isEmpty()) { + Date untilTime = new Date(); + Date sinceTime = new Date(geogit.command(ParseTimestamp.class).setString(since).call()); + logOp.setTimeRange(new Range(Date.class, sinceTime, untilTime)); + } + if (this.until != null) { + Optional until; + until = geogit.command(RevParse.class).setRefSpec(this.until).call(); + Preconditions.checkArgument(until.isPresent(), "Object not found '%s'", this.until); + logOp.setUntil(until.get()); + } + + LsTreeOp lsTreeOp = geogit.command(LsTreeOp.class) + .setStrategy(LsTreeOp.Strategy.TREES_ONLY); + if (path != null && !path.trim().isEmpty()) { + lsTreeOp.setReference(path); + logOp.addPath(path); + } + final Iterator treeIter = lsTreeOp.call(); + + while (treeIter.hasNext()) { + NodeRef node = treeIter.next(); + stats.add(new featureTypeStats(node.path(), context.getGeoGIT().getRepository() + .getTree(node.objectId()).size())); + } + log = logOp.call(); + + RevCommit firstCommit = null; + RevCommit lastCommit = null; + int totalCommits = 0; + final List authors = Lists.newArrayList(); + + if (log.hasNext()) { + lastCommit = log.next(); + totalCommits++; + } + while (log.hasNext()) { + firstCommit = log.next(); + RevPerson newAuthor = firstCommit.getAuthor(); + boolean authorFound = false; + for (RevPerson author : authors) { + if (newAuthor.getName().equals(author.getName()) + && newAuthor.getEmail().equals(author.getEmail())) { + authorFound = true; + break; + } + } + if (!authorFound) { + authors.add(newAuthor); + } + totalCommits++; + } + int addedFeatures = 0; + int modifiedFeatures = 0; + int removedFeatures = 0; + if (since != null && !since.trim().isEmpty() && firstCommit != null && lastCommit != null) { + final Iterator diff = geogit.command(DiffOp.class) + .setOldVersion(firstCommit.getId()).setNewVersion(lastCommit.getId()) + .setFilter(path).call(); + while (diff.hasNext()) { + DiffEntry entry = diff.next(); + if (entry.changeType() == DiffEntry.ChangeType.ADDED) { + addedFeatures++; + } else if (entry.changeType() == DiffEntry.ChangeType.MODIFIED) { + modifiedFeatures++; + } else { + removedFeatures++; + } + } + } + + final RevCommit first = firstCommit; + final RevCommit last = lastCommit; + final int total = totalCommits; + final int added = addedFeatures; + final int modified = modifiedFeatures; + final int removed = removedFeatures; + context.setResponseContent(new CommandResponse() { + @Override + public void write(ResponseWriter out) throws Exception { + out.start(true); + out.writeStatistics(stats, first, last, total, authors, added, modified, removed); + out.finish(); + } + }); + } + + public class featureTypeStats { + long numFeatures; + + String featureTypeName; + + public featureTypeStats(String name, long numFeatures) { + this.numFeatures = numFeatures; + this.featureTypeName = name; + } + + public long getNumFeatures() { + return numFeatures; + } + + public String getName() { + return featureTypeName; + } + }; +}