Author: bdelacretaz
Date: Mon Dec 23 12:13:35 2013
New Revision: 1553100
URL: http://svn.apache.org/r1553100
Log:
SLING-3297 - add my (work in progress) stats module
Added:
sling/trunk/samples/mail-archive/stats/ (with props)
sling/trunk/samples/mail-archive/stats/pom.xml
sling/trunk/samples/mail-archive/stats/src/
sling/trunk/samples/mail-archive/stats/src/main/
sling/trunk/samples/mail-archive/stats/src/main/java/
sling/trunk/samples/mail-archive/stats/src/main/java/org/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
sling/trunk/samples/mail-archive/stats/src/main/resources/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
(with props)
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
(with props)
Modified:
sling/trunk/samples/mail-archive/pom.xml
Modified: sling/trunk/samples/mail-archive/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/pom.xml?rev=1553100&r1=1553099&r2=1553100&view=diff
==============================================================================
--- sling/trunk/samples/mail-archive/pom.xml (original)
+++ sling/trunk/samples/mail-archive/pom.xml Mon Dec 23 12:13:35 2013
@@ -11,5 +11,6 @@
<modules>
<module>server</module>
<module>ui</module>
+ <module>stats</module>
</modules>
</project>
Propchange: sling/trunk/samples/mail-archive/stats/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Dec 23 12:13:35 2013
@@ -0,0 +1,13 @@
+target
+bin
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+
+
+
Added: sling/trunk/samples/mail-archive/stats/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/pom.xml?rev=1553100&view=auto
==============================================================================
--- sling/trunk/samples/mail-archive/stats/pom.xml (added)
+++ sling/trunk/samples/mail-archive/stats/pom.xml Mon Dec 23 12:13:35 2013
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>18</version>
+ </parent>
+
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.mailarchive.stats</artifactId>
+ <packaging>bundle</packaging>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <name>Apache Sling Mail Archive Server Stats</name>
+ <inceptionYear>2013</inceptionYear>
+
+ <description>
+ Stats module for the mail archive server
+ </description>
+
+ <properties>
+ <sling.java.version>6</sling.java.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Sling-Initial-Content>
+ initial-content; uninstall:=true
+ </Sling-Initial-Content>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>1.6.2</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.4.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.json</artifactId>
+ <version>2.0.6</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling.mailarchiveserver</groupId>
+ <artifactId>server</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.james</groupId>
+ <artifactId>apache-mime4j-core</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.james</groupId>
+ <artifactId>apache-mime4j-mbox-iterator</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.james</groupId>
+ <artifactId>apache-mime4j-dom</artifactId>
+ <version>0.8.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/MailStatsProcessor.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,17 @@
+package org.apache.sling.mailarchive.stats;
+
+import java.util.Date;
+
+/** Compute and store stats */
+public interface MailStatsProcessor {
+ /**
+ * @param date Message timestamp
+ * @param from "From "address of the message
+ * @param to Optional "to" addresses
+ * @param cc Option "Cc" addresses
+ */
+ void computeStats(Date d, String from, String [] to, String [] cc);
+
+ /** Flush in-memory data to permanent storage */
+ void flush();
+}
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/OrgMapper.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,6 @@
+package org.apache.sling.mailarchive.stats;
+
+/** Maps an email address to an organization name */
+public interface OrgMapper {
+ String mapToOrg(String email);
+}
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/MailStatsProcessorImpl.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,210 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.mailarchive.stats.MailStatsProcessor;
+import org.apache.sling.mailarchive.stats.OrgMapper;
+import org.apache.sling.mailarchiveserver.api.MessageProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service
+/** Computes stats of how often a given organization writes to
+ * another one in a given time period which is defined by
+ * a Date formatter. Using a formatter that uses only year and
+ * month, for example, yields per-month data.
+ */
+public class MailStatsProcessorImpl implements MailStatsProcessor,
MessageProcessor {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference
+ private OrgMapper orgMapper;
+
+ @Reference
+ private ResourceResolverFactory resourceResolverFactory;
+
+ // TODO configurable format
+ final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM");
+
+ // TODO configurable root path
+ private static final String ROOT_PATH =
"/content/mailarchiveserver/stats";
+
+ public static final String DEFAULT_RESOURCE_TYPE = "mailserver/stats";
+ public static final String DESTINATION_RESOURCE_TYPE =
"mailserver/stats/destination";
+ public static final String DATA_RESOURCE_TYPE = "mailserver/stats/data";
+ public static final String PERIOD_PROP = "period";
+ private static final String [] EMPTY_STRING_ARRAY = new String[]{};
+ static final String SOURCE_PROP_PREFIX = "FROM_";
+
+ // We need to count the number of messages to a destination,
+ // per formatted timestamp and source
+ private final Map<String, DataRecord> data = new HashMap<String,
DataRecord>();
+
+ class DataRecord {
+ final String destination;
+ final Map<String, Integer> sourceCounts = new HashMap<String,
Integer>();
+ final String timestampPath;
+
+ DataRecord(Date d, String destination) {
+ this.destination = orgMapper.mapToOrg(destination);
+ synchronized (dateFormat) {
+ this.timestampPath = dateFormat.format(d);
+ }
+ }
+
+ Map<String, Integer> getSourceCounts() {
+ return sourceCounts;
+ }
+
+ void increment(String source) {
+ source = SOURCE_PROP_PREFIX + orgMapper.mapToOrg(source);
+ Integer count = sourceCounts.get(source);
+ if(count == null) {
+ count = 1;
+ } else {
+ count = count.intValue() + 1;
+ }
+ sourceCounts.put(source, count);
+ }
+
+ public String getOrgPath() {
+ return ROOT_PATH + "/" + destination;
+ }
+
+ public String getPath() {
+ return getOrgPath() + "/" + timestampPath;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append(getClass().getSimpleName())
+ .append(' ')
+ .append(timestampPath)
+ .append(' ')
+ .append(destination)
+ .append(' ')
+ .append(sourceCounts)
+ .toString();
+ }
+
+ String getKey() {
+ return new StringBuilder()
+ .append(timestampPath)
+ .append('#')
+ .append(destination)
+ .toString();
+ }
+ }
+
+ public void computeStats(Date d, String from, String [] to, String [] cc) {
+ if(to != null) {
+ for(String dest : to) {
+ addRecord(d, from, dest);
+ }
+ }
+ if(cc != null) {
+ for(String dest : cc) {
+ addRecord(d, from, dest);
+ }
+ }
+ }
+
+ private void addRecord(Date d, String from, String to) {
+ final DataRecord r = new DataRecord(d, to);
+ synchronized (data) {
+ final DataRecord existing = data.get(r.getKey());
+ if(existing == null) {
+ r.increment(from);
+ data.put(r.getKey(), r);
+ } else {
+ existing.increment(from);
+ }
+ }
+ }
+
+ /** Called by the mail archive server store before storing messages -
+ * we hook into this to compute our stats.
+ */
+ @Override
+ public void processMessage(Message m) {
+ log.debug("Processing {}", m);
+ final String [] to = toArray(m.getTo());
+ final String [] cc = toArray(m.getCc());
+ for(String from : MailStatsProcessorImpl.toArray(m.getFrom())) {
+ computeStats(m.getDate(), from.toString(), to, cc);
+ }
+
+ // TODO call this async and less often?
+ flush();
+ }
+
+ /** Flush in-memory data to permanent storage */
+ public void flush() {
+ try {
+ ResourceResolver resolver = null;
+ try {
+ resolver =
resourceResolverFactory.getAdministrativeResourceResolver(null);
+ for(DataRecord r : data.values()) {
+ // Each org gets its own resource under our root
+ log.info("Storing {} at path {}", r, r.getPath());
+ ResourceUtil.getOrCreateResource(resolver, ROOT_PATH,
DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, false);
+ ResourceUtil.getOrCreateResource(resolver, r.getOrgPath(),
DESTINATION_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, false);
+
+ // Properties are the message counts per source for this
destination
+ final Map<String, Object> data = new HashMap<String,
Object>();
+ for(Map.Entry<String, Integer> e :
r.getSourceCounts().entrySet()) {
+ data.put(e.getKey(), e.getValue());
+ }
+ data.put(PERIOD_PROP, r.timestampPath);
+ data.put("sling:resourceType", DATA_RESOURCE_TYPE);
+
+ // TODO for now this overwrites existing values,
+ // need to combine existing and new
+ ResourceUtil.getOrCreateResource(resolver, r.getPath(),
data, DEFAULT_RESOURCE_TYPE, false);
+
+ }
+ data.clear();
+ } finally {
+ if(resolver != null) {
+ resolver.commit();
+ resolver.close();
+ }
+ }
+ } catch(Exception e) {
+ log.warn("Exception in flush()", e);
+ }
+ }
+
+ // TODO don't we have a utility for that?
+ static String makeJcrFriendly(String s) {
+ return s.replaceAll("[\\s\\.-]", "_").replaceAll("\\W",
"").replaceAll("\\_", " ").trim().replaceAll(" ", "_");
+ }
+
+ static String [] toArray(AbstractList<?> list) {
+ if(list == null) {
+ return null;
+ }
+ final List<String> result = new ArrayList<String>();
+ for(Object o : list) {
+ result.add(o.toString());
+ }
+ return result.toArray(EMPTY_STRING_ARRAY);
+ }
+
+}
\ No newline at end of file
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/OrgMapperImpl.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,18 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.mailarchive.stats.OrgMapper;
+
+@Component
+@Service
+public class OrgMapperImpl implements OrgMapper {
+
+ @Override
+ public String mapToOrg(String email) {
+ if(email.contains("@")) {
+ return email.split("@")[1];
+ }
+ return email;
+ }
+}
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsDataServlet.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,124 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.servlet.ServletException;
+
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.io.JSONWriter;
+
+@SuppressWarnings("serial")
+@SlingServlet(
+ resourceTypes = "mailserver/stats/destination",
+ methods = "GET",
+ extensions="json",
+ selectors="statsdata")
+/** Generates json data for the destination.esp script.
+ *
+ * Produces output like
+ * <pre>
+ *
+ * var statsData = [
+ * { "period": "2012/01",
+ * "senders" : {
+ * "netenviron.com" : 1,
+ * "builds.apache.org" : 12
+ * }},
+ * { "period": "2013/09",
+ * "senders" : {
+ * "yahoo.com" : 32,
+ * "netenviron.com" : 6,
+ * "builds.apache.org" : 9
+ * }}
+ * ];
+ * var layers = [
+ * "yahoo.com",
+ * "netenviron.com",
+ * "builds.apache.org"
+ * ];
+ *
+ * </pre>
+ *
+ */
+public class StatsDataServlet extends SlingSafeMethodsServlet {
+
+ @Override
+ protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response)
+ throws ServletException, IOException {
+
+ response.setCharacterEncoding("UTF-8");
+ final PrintWriter out = response.getWriter();
+
+ try {
+ out.write("// data provided by " + getClass().getName() + "\n");
+ final SortedSet<String> layers = new TreeSet<String>();
+
+ // Visit our child resources and build the statsData object
+ // from those that have the stats data resource type
+ {
+ final JSONWriter w = new JSONWriter(response.getWriter());
+ w.setTidy(true);
+ out.write("var statsData = ");
+ w.array();
+ dumpStatsData(request.getResource(), w, layers);
+ w.endArray();
+ out.flush();
+ out.write(";\n");
+ }
+
+ // Output the layers array in JSON
+ {
+ final JSONWriter w = new JSONWriter(response.getWriter());
+ w.setTidy(true);
+ out.write("var layers = ");
+ w.array();
+ for(String layer : layers) {
+ w.value(layer);
+ };
+ w.endArray();
+ out.write(";");
+ }
+
+ } catch(JSONException je) {
+ throw new ServletException("JSONException in doGet", je);
+ }
+ }
+
+ /** Dump stats data to w if r is a stats data resource,
+ * and recurse into children
+ */
+ private void dumpStatsData(Resource r, JSONWriter w, Set<String> layers)
throws JSONException {
+
if(MailStatsProcessorImpl.DATA_RESOURCE_TYPE.equals(r.getResourceType())) {
+ final ValueMap m = r.adaptTo(ValueMap.class);
+ if(m != null) {
+ w.object();
+
w.key("period").value(m.get(MailStatsProcessorImpl.PERIOD_PROP, "NO_PERIOD"));
+ w.key("senders");
+ w.object();
+ for(String key : m.keySet()) {
+
if(key.startsWith(MailStatsProcessorImpl.SOURCE_PROP_PREFIX)) {
+ final String source =
key.substring(MailStatsProcessorImpl.SOURCE_PROP_PREFIX.length());
+ layers.add(source);
+ w.key(source).value(m.get(key));
+ }
+ }
+ w.endObject();
+ w.endObject();
+ }
+ }
+
+ for(Resource child : r.getChildren()) {
+ dumpStatsData(child, w, layers);
+ }
+ }
+}
\ No newline at end of file
Added:
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/java/org/apache/sling/mailarchive/stats/impl/StatsTestServlet.java
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,76 @@
+package org.apache.sling.mailarchive.stats.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Iterator;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.mailarchive.stats.MailStatsProcessor;
+import org.apache.sling.mailarchiveserver.api.MboxParser;
+
+@SuppressWarnings("serial")
+@SlingServlet(
+ resourceTypes = "mailarchiveserver/import",
+ methods = {"POST", "PUT"},
+ selectors="stats")
+/** Test the stats import with
+ * curl -u admin:admin -XPOST [email protected]
http://localhost:8080/content/mailarchiveserver/import.stats.txt
+ */
+public class StatsTestServlet extends SlingAllMethodsServlet {
+
+ private static final String IMPORT_FILE_ATTRIB_NAME = "mboxfile";
+
+ @Reference
+ private MboxParser parser;
+
+ @Reference
+ private MailStatsProcessor processor;
+
+ @Override
+ protected void doPost(SlingHttpServletRequest request,
SlingHttpServletResponse response)
+ throws ServletException, IOException {
+ final RequestParameter param =
request.getRequestParameter(IMPORT_FILE_ATTRIB_NAME);
+ if(param == null) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing
required parameter " + IMPORT_FILE_ATTRIB_NAME);
+ return;
+ }
+
+ InputStream is = null;
+ final PrintWriter pw = response.getWriter();
+ response.setContentType("text/plain");
+ response.setCharacterEncoding("UTF-8");
+
+ try {
+ is = param.getInputStream();
+ pw.println("Creating stats from supplied mbox file...");
+ int counter=0;
+ final Iterator<Message> it = parser.parse(is);
+ while(it.hasNext()) {
+ final Message m = it.next();
+ final String [] to = MailStatsProcessorImpl.toArray(m.getTo());
+ final String [] cc = MailStatsProcessorImpl.toArray(m.getCc());
+ for(String from : MailStatsProcessorImpl.toArray(m.getFrom()))
{
+ processor.computeStats(m.getDate(), from.toString(), to,
cc);
+ }
+ counter++;
+ }
+ pw.println(counter + " messages parsed");
+ } finally {
+ processor.flush();
+ pw.flush();
+ if(is != null) {
+ is.close();
+ }
+ }
+ }
+}
\ No newline at end of file
Added:
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+
+<%
+// d3js.org rendering of a destination's stats
+// based on the http://bl.ocks.org/mbostock/3943967 example
+
+var title = resource.path.replace(/.*\/(.*)/g,"$1")
+%>
+
+<title><%= title %></title>
+
+<style>
+
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: auto;
+ position: relative;
+ width: 960px;
+}
+
+text {
+ font: 10px sans-serif;
+}
+
+.axis path,
+.axis line {
+ fill: none;
+ stroke: #000;
+ shape-rendering: crispEdges;
+}
+
+form {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+
+</style>
+
+<script src="http://d3js.org/d3.v3.min.js"></script>
+</head>
+
+<body>
+<h1><%= title %></h1>
+<form>
+ <label><input type="radio" name="mode" value="grouped"> Grouped</label>
+ <label><input type="radio" name="mode" value="stacked" checked>
Stacked</label>
+</form>
+<div id="graph"/>
+
+<script>
+<% sling.include(resource.path + ".statsdata.json"); %>
+</script>
+
+<script>
+
+// number of layers and samples per layer
+var n = layers.length;
+var m = statsData.length;
+
+var stack = d3.layout.stack(),
+ layers = stack(d3.range(n).map(function(n) { return layerData(m, n); })),
+ yGroupMax = d3.max(layers, function(layer) { return d3.max(layer,
function(d) { return d.y; }); }),
+ yStackMax = d3.max(layers, function(layer) { return d3.max(layer,
function(d) { return d.y0 + d.y; }); });
+
+var margin = {top: 40, right: 10, bottom: 20, left: 10},
+ width = 960 - margin.left - margin.right,
+ height = 500 - margin.top - margin.bottom;
+
+var x = d3.scale.ordinal()
+ .domain(d3.range(m))
+ .rangeRoundBands([0, width], .08);
+
+var y = d3.scale.linear()
+ .domain([0, yStackMax])
+ .range([height, 0]);
+
+var color = d3.scale.linear()
+ .domain([0, n - 1])
+ .range(["#aad", "#556"]);
+
+var xAxis = d3.svg.axis()
+ .scale(x)
+ .tickSize(0)
+ .tickPadding(6)
+ .orient("bottom")
+ .tickFormat(xTickFormat);
+
+var svg = d3.select("#graph").append("svg")
+ .attr("width", width + margin.left + margin.right)
+ .attr("height", height + margin.top + margin.bottom)
+ .append("g")
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+var layer = svg.selectAll(".layer")
+ .data(layers)
+ .enter().append("g")
+ .attr("class", "layer")
+ .style("fill", function(d, i) { return color(i); });
+
+var rect = layer.selectAll("rect")
+ .data(function(d) { return d; })
+ .enter().append("rect")
+ .attr("x", function(d) { return x(d.x); })
+ .attr("y", height)
+ .attr("width", x.rangeBand())
+ .attr("height", 0);
+
+rect.transition()
+ .delay(function(d, i) { return i * 10; })
+ .attr("y", function(d) { return y(d.y0 + d.y); })
+ .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
+
+svg.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + height + ")")
+ .call(xAxis);
+
+d3.selectAll("input").on("change", change);
+
+var timeout = setTimeout(function() {
+ d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
+}, 2000);
+
+function change() {
+ clearTimeout(timeout);
+ if (this.value === "grouped") transitionGrouped();
+ else transitionStacked();
+}
+
+function transitionGrouped() {
+ y.domain([0, yGroupMax]);
+
+ rect.transition()
+ .duration(500)
+ .delay(function(d, i) { return i * 10; })
+ .attr("x", function(d, i, j) { return x(d.x) + x.rangeBand() / n * j; })
+ .attr("width", x.rangeBand() / n)
+ .transition()
+ .attr("y", function(d) { return y(d.y); })
+ .attr("height", function(d) { return height - y(d.y); });
+}
+
+function transitionStacked() {
+ y.domain([0, yStackMax]);
+
+ rect.transition()
+ .duration(500)
+ .delay(function(d, i) { return i * 10; })
+ .attr("y", function(d) { return y(d.y0 + d.y); })
+ .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
+ .transition()
+ .attr("x", function(d) { return x(d.x); })
+ .attr("width", x.rangeBand());
+}
+
+// Format our tick values
+function xTickFormat(index) {
+ return statsData[index].period;
+}
+
+function layerValue(layerIndex, itemIndex) {
+ var layerName = layers[layerIndex];
+ var senders = statsData[itemIndex].senders;
+ if(senders[layerName]) {
+ return senders[layerName];
+ }
+ return -1;
+}
+
+// Generate the data of a given layer
+function layerData(nElements, layerIndex) {
+ var a = [];
+ for (i = 0; i < nElements; ++i) a[i] = i;
+ return a.map(function(d,i) { return { x:i, y: layerValue(layerIndex,i) }});
+}
+
+</script>
+</body>
\ No newline at end of file
Propchange:
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/destination/destination.esp
------------------------------------------------------------------------------
svn:executable = *
Added:
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
URL:
http://svn.apache.org/viewvc/sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp?rev=1553100&view=auto
==============================================================================
---
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
(added)
+++
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
Mon Dec 23 12:13:35 2013
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+
+<%
+var title = "Mail archive stats prototype"
+%>
+
+<head>
+<meta charset="utf-8">
+<title><%= title %></title>
+</head>
+
+<body>
+<h1><%= title %></h1>
+
+Statistics are available for the following destinations:
+
+<ul>
+<%
+it = resource.getResourceResolver().listChildren(resource);
+while(it.hasNext()) {
+ child = it.next();
+ %>
+ <li>
+ <a href="<%= child.getPath() %>.html">
+ <%= child.getPath().replace(/.*\/(.*)/g,"$1") %>
+ </a>
+ </li>
+ <%
+}
+%>
+
+</ul>
+</script>
+</body>
\ No newline at end of file
Propchange:
sling/trunk/samples/mail-archive/stats/src/main/resources/initial-content/apps/mailserver/stats/stats.esp
------------------------------------------------------------------------------
svn:executable = *